/*
 * file:       Task.java
 * author:     Scott Melville
 *             Jon Iles
 * copyright:  (c) Packwood Software 2002-2003
 * date:       15/08/2002
 */

/*
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

package net.sf.mpxj;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import net.sf.mpxj.common.BooleanHelper;
import net.sf.mpxj.common.DateHelper;
import net.sf.mpxj.common.NumberHelper;
import net.sf.mpxj.common.TaskFieldLists;
import net.sf.mpxj.listener.FieldListener;

/**
 * This class represents a task record from an project file.
 */
public final class Task extends ProjectEntity implements Comparable<Task>, ProjectEntityWithID, FieldContainer, ChildTaskContainer
{
   /**
    * Default constructor.
    *
    * @param file Parent file to which this record belongs.
    * @param parent Parent task
    */
   Task(ProjectFile file, Task parent)
   {
      super(file);

      setType(TaskType.FIXED_UNITS);
      setConstraintType(ConstraintType.AS_SOON_AS_POSSIBLE);
      setTaskMode(TaskMode.AUTO_SCHEDULED);
      setActive(true);
      set(TaskField.PREDECESSORS, new ArrayList<Relation>());
      set(TaskField.SUCCESSORS, new ArrayList<Relation>());

      m_parent = parent;
      ProjectConfig config = file.getProjectConfig();

      if (config.getAutoTaskUniqueID() == true)
      {
         setUniqueID(Integer.valueOf(config.getNextTaskUniqueID()));
      }

      if (config.getAutoTaskID() == true)
      {
         setID(Integer.valueOf(config.getNextTaskID()));
      }

      if (config.getAutoWBS() == true)
      {
         generateWBS(parent);
      }

      if (config.getAutoOutlineNumber() == true)
      {
         generateOutlineNumber(parent);
      }

      if (config.getAutoOutlineLevel() == true)
      {
         if (parent == null)
         {
            setOutlineLevel(Integer.valueOf(1));
         }
         else
         {
            setOutlineLevel(Integer.valueOf(NumberHelper.getInt(parent.getOutlineLevel()) + 1));
         }
      }
   }

   /**
    * This method is used to automatically generate a value
    * for the WBS field of this task.
    *
    * @param parent Parent Task
    */
   public void generateWBS(Task parent)
   {
      String wbs;

      if (parent == null)
      {
         if (NumberHelper.getInt(getUniqueID()) == 0)
         {
            wbs = "0";
         }
         else
         {
            wbs = Integer.toString(getParentFile().getChildTasks().size() + 1);
         }
      }
      else
      {
         wbs = parent.getWBS();

         //
         // Apparently I added the next lines to support MPX files generated by Artemis, back in 2005
         // Unfortunately I have no test data which exercises this code, and it now breaks
         // otherwise valid WBS values read (in this case) from XER files. So it's commented out
         // until someone complains about their 2005-era Artemis MPX files not working!
         //
         //         int index = wbs.lastIndexOf(".0");
         //         if (index != -1)
         //         {
         //            wbs = wbs.substring(0, index);
         //         }

         int childTaskCount = parent.getChildTasks().size() + 1;
         if (wbs.equals("0"))
         {
            wbs = Integer.toString(childTaskCount);
         }
         else
         {
            wbs += ("." + childTaskCount);
         }
      }

      setWBS(wbs);
   }

   /**
    * This method is used to automatically generate a value
    * for the Outline Number field of this task.
    *
    * @param parent Parent Task
    */
   public void generateOutlineNumber(Task parent)
   {
      String outline;

      if (parent == null)
      {
         if (NumberHelper.getInt(getUniqueID()) == 0)
         {
            outline = "0";
         }
         else
         {
            outline = Integer.toString(getParentFile().getChildTasks().size() + 1);
         }
      }
      else
      {
         outline = parent.getOutlineNumber();

         int index = outline.lastIndexOf(".0");

         if (index != -1)
         {
            outline = outline.substring(0, index);
         }

         int childTaskCount = parent.getChildTasks().size() + 1;
         if (outline.equals("0"))
         {
            outline = Integer.toString(childTaskCount);
         }
         else
         {
            outline += ("." + childTaskCount);
         }
      }

      setOutlineNumber(outline);
   }

   /**
    * This method is used to add notes to the current task.
    *
    * @param notes notes to be added
    */
   public void setNotes(String notes)
   {
      set(TaskField.NOTES, notes);
   }

   /**
    * This method allows nested tasks to be added, with the WBS being
    * completed automatically.
    *
    * @return new task
    */
   @Override public Task addTask()
   {
      ProjectFile parent = getParentFile();

      Task task = new Task(parent, this);

      m_children.add(task);

      parent.getTasks().add(task);

      setSummary(true);

      return (task);
   }

   /**
    * This method is used to associate a child task with the current
    * task instance. It has package access, and has been designed to
    * allow the hierarchical outline structure of tasks in an MPX
    * file to be constructed as the file is read in.
    *
    * @param child Child task.
    * @param childOutlineLevel Outline level of the child task.
    */
   public void addChildTask(Task child, int childOutlineLevel)
   {
      int outlineLevel = NumberHelper.getInt(getOutlineLevel());

      if ((outlineLevel + 1) == childOutlineLevel)
      {
         m_children.add(child);
         setSummary(true);
      }
      else
      {
         if (m_children.isEmpty() == false)
         {
            (m_children.get(m_children.size() - 1)).addChildTask(child, childOutlineLevel);
         }
      }
   }

   /**
    * This method is used to associate a child task with the current
    * task instance. It has been designed to
    * allow the hierarchical outline structure of tasks in an MPX
    * file to be updated once all of the task data has been read.
    *
    * @param child child task
    */
   public void addChildTask(Task child)
   {
      child.m_parent = this;
      m_children.add(child);
      setSummary(true);

      if (getParentFile().getProjectConfig().getAutoOutlineLevel() == true)
      {
         child.setOutlineLevel(Integer.valueOf(NumberHelper.getInt(getOutlineLevel()) + 1));
      }
   }

   /**
    * Inserts a child task prior to a given sibling task.
    *
    * @param child new child task
    * @param previousSibling sibling task
    */
   public void addChildTaskBefore(Task child, Task previousSibling)
   {
      int index = m_children.indexOf(previousSibling);
      if (index == -1)
      {
         m_children.add(child);
      }
      else
      {
         m_children.add(index, child);
      }

      child.m_parent = this;
      setSummary(true);

      if (getParentFile().getProjectConfig().getAutoOutlineLevel() == true)
      {
         child.setOutlineLevel(Integer.valueOf(NumberHelper.getInt(getOutlineLevel()) + 1));
      }
   }

   /**
    * Removes a child task.
    *
    * @param child child task instance
    */
   public void removeChildTask(Task child)
   {
      if (m_children.remove(child))
      {
         child.m_parent = null;
      }
      setSummary(!m_children.isEmpty());
   }

   /**
    * This method allows the list of child tasks to be cleared in preparation
    * for the hierarchical task structure to be built.
    */
   public void clearChildTasks()
   {
      m_children.clear();
      setSummary(false);
   }

   /**
    * This method allows recurring task details to be added to the
    * current task.
    *
    * @return RecurringTask object
    */
   public RecurringTask addRecurringTask()
   {
      if (m_recurringTask == null)
      {
         m_recurringTask = new RecurringTask();
      }

      return (m_recurringTask);
   }

   /**
    * This method retrieves the recurring task record. If the current
    * task is not a recurring task, then this method will return null.
    *
    * @return Recurring task record.
    */
   public RecurringTask getRecurringTask()
   {
      return (m_recurringTask);
   }

   /**
    * Retrieve the activity codes associated with this task.
    *
    * @return list of activity codes
    */
   public List<ActivityCodeValue> getActivityCodes()
   {
      return m_activityCodes;
   }

   /**
    * Assign an activity code to this task.
    *
    * @param value activity coe value
    */
   public void addActivityCode(ActivityCodeValue value)
   {
      m_activityCodes.add(value);
   }

   /**
    * This method allows a resource assignment to be added to the
    * current task.
    *
    * @param resource the resource to assign
    * @return ResourceAssignment object
    */
   public ResourceAssignment addResourceAssignment(Resource resource)
   {
      ResourceAssignment assignment = getExistingResourceAssignment(resource);

      if (assignment == null)
      {
         assignment = new ResourceAssignment(getParentFile(), this);
         m_assignments.add(assignment);
         getParentFile().getResourceAssignments().add(assignment);

         assignment.setTaskUniqueID(getUniqueID());
         assignment.setWork(getDuration());
         assignment.setUnits(ResourceAssignment.DEFAULT_UNITS);

         if (resource != null)
         {
            assignment.setResourceUniqueID(resource.getUniqueID());
            resource.addResourceAssignment(assignment);
         }
      }

      return (assignment);
   }

   /**
    * Add a resource assignment which has been populated elsewhere.
    *
    * @param assignment resource assignment
    */
   public void addResourceAssignment(ResourceAssignment assignment)
   {
      if (getExistingResourceAssignment(assignment.getResource()) == null)
      {
         m_assignments.add(assignment);
         getParentFile().getResourceAssignments().add(assignment);

         Resource resource = assignment.getResource();
         if (resource != null)
         {
            resource.addResourceAssignment(assignment);
         }
      }
   }

   /**
    * Retrieves an existing resource assignment if one is present,
    * to prevent duplicate resource assignments being added.
    *
    * @param resource resource to test for
    * @return existing resource assignment
    */
   private ResourceAssignment getExistingResourceAssignment(Resource resource)
   {
      ResourceAssignment assignment = null;
      Integer resourceUniqueID = null;

      if (resource != null)
      {
         Iterator<ResourceAssignment> iter = m_assignments.iterator();
         resourceUniqueID = resource.getUniqueID();

         while (iter.hasNext() == true)
         {
            assignment = iter.next();
            Integer uniqueID = assignment.getResourceUniqueID();
            if (uniqueID != null && uniqueID.equals(resourceUniqueID) == true)
            {
               break;
            }
            assignment = null;
         }
      }

      return assignment;
   }

   /**
    * This method allows the list of resource assignments for this
    * task to be retrieved.
    *
    * @return list of resource assignments
    */
   public List<ResourceAssignment> getResourceAssignments()
   {
      return (m_assignments);
   }

   /**
    * Internal method used as part of the process of removing a
    * resource assignment.
    *
    * @param assignment resource assignment to be removed
    */
   void removeResourceAssignment(ResourceAssignment assignment)
   {
      m_assignments.remove(assignment);
   }

   /**
    * This method allows a predecessor relationship to be added to this
    * task instance.
    *
    * @param targetTask the predecessor task
    * @param type relation type
    * @param lag relation lag
    * @return relationship
    */
   @SuppressWarnings("unchecked") public Relation addPredecessor(Task targetTask, RelationType type, Duration lag)
   {
      //
      // Ensure that we have a valid lag duration
      //
      if (lag == null)
      {
         lag = Duration.getInstance(0, TimeUnit.DAYS);
      }

      //
      // Retrieve the list of predecessors
      //
      List<Relation> predecessorList = (List<Relation>) getCachedValue(TaskField.PREDECESSORS);

      //
      // Ensure that there is only one predecessor relationship between
      // these two tasks.
      //
      Relation predecessorRelation = null;
      Iterator<Relation> iter = predecessorList.iterator();
      while (iter.hasNext() == true)
      {
         predecessorRelation = iter.next();
         if (predecessorRelation.getTargetTask() == targetTask)
         {
            if (predecessorRelation.getType() != type || predecessorRelation.getLag().compareTo(lag) != 0)
            {
               predecessorRelation = null;
            }
            break;
         }
         predecessorRelation = null;
      }

      //
      // If necessary, create a new predecessor relationship
      //
      if (predecessorRelation == null)
      {
         predecessorRelation = new Relation(this, targetTask, type, lag);
         predecessorList.add(predecessorRelation);
      }

      //
      // Retrieve the list of successors
      //
      List<Relation> successorList = (List<Relation>) targetTask.getCachedValue(TaskField.SUCCESSORS);

      //
      // Ensure that there is only one successor relationship between
      // these two tasks.
      //
      Relation successorRelation = null;
      iter = successorList.iterator();
      while (iter.hasNext() == true)
      {
         successorRelation = iter.next();
         if (successorRelation.getTargetTask() == this)
         {
            if (successorRelation.getType() != type || successorRelation.getLag().compareTo(lag) != 0)
            {
               successorRelation = null;
            }
            break;
         }
         successorRelation = null;
      }

      //
      // If necessary, create a new successor relationship
      //
      if (successorRelation == null)
      {
         successorRelation = new Relation(targetTask, this, type, lag);
         successorList.add(successorRelation);
      }

      return (predecessorRelation);
   }

   /**
    * The % Complete field contains the current status of a task, expressed
    * as the percentage of the
    * task's duration that has been completed. You can enter percent complete,
    * or you can have
    * Microsoft Project calculate it for you based on actual duration.
    *
    * @param val value to be set
    */
   public void setPercentageComplete(Number val)
   {
      set(TaskField.PERCENT_COMPLETE, val);
   }

   /**
    * The % Work Complete field contains the current status of a task,
    * expressed as the
    * percentage of the task's work that has been completed. You can enter
    * percent work
    * complete, or you can have Microsoft Project calculate it for you
    * based on actual
    * work on the task.
    *
    * @param val value to be set
    */
   public void setPercentageWorkComplete(Number val)
   {
      set(TaskField.PERCENT_WORK_COMPLETE, val);
   }

   /**
    * The Actual Cost field shows costs incurred for work already performed
    * by all resources
    * on a task, along with any other recorded costs associated with the task.
    * You can enter
    * all the actual costs or have Microsoft Project calculate them for you.
    *
    * @param val value to be set
    */
   public void setActualCost(Number val)
   {
      set(TaskField.ACTUAL_COST, val);
   }

   /**
    * The Actual Duration field shows the span of actual working time for a
    * task so far,
    * based on the scheduled duration and current remaining work or
    * completion percentage.
    *
    * @param val value to be set
    */
   public void setActualDuration(Duration val)
   {
      set(TaskField.ACTUAL_DURATION, val);
   }

   /**
    * The Actual Finish field shows the date and time that a task actually
    * finished.
    * Microsoft Project sets the Actual Finish field to the scheduled finish
    * date if
    * the completion percentage is 100. This field contains "NA" until you
    * enter actual
    * information or set the completion percentage to 100.
    *
    * @param val value to be set
    */
   public void setActualFinish(Date val)
   {
      set(TaskField.ACTUAL_FINISH, val);
   }

   /**
    * The Actual Start field shows the date and time that a task actually began.
    * When a task is first created, the Actual Start field contains "NA." Once you
    * enter the first actual work or a completion percentage for a task, Microsoft
    * Project sets the actual start date to the scheduled start date.
    * @param val value to be set
    */
   public void setActualStart(Date val)
   {
      set(TaskField.ACTUAL_START, val);
   }

   /**
    * The Actual Work field shows the amount of work that has already been
    * done by the
    * resources assigned to a task.
    * @param val value to be set
    */
   public void setActualWork(Duration val)
   {
      set(TaskField.ACTUAL_WORK, val);
   }

   /**
    * The Baseline Cost field shows the total planned cost for a task.
    * Baseline cost is also referred to as budget at completion (BAC).
    *
    * @param val the amount to be set
    */
   public void setBaselineCost(Number val)
   {
      set(TaskField.BASELINE_COST, val);
   }

   /**
    * The Baseline Duration field shows the original span of time planned to
    * complete a task.
    *
    * @param val duration
    */
   public void setBaselineDuration(Duration val)
   {
      set(TaskField.BASELINE_DURATION, val);
   }

   /**
    * The Baseline Finish field shows the planned completion date for a
    * task at the time
    * you saved a baseline. Information in this field becomes available
    * when you set a
    * baseline for a task.
    *
    * @param val Date to be set
    */
   public void setBaselineFinish(Date val)
   {
      set(TaskField.BASELINE_FINISH, val);
   }

   /**
    * The Baseline Start field shows the planned beginning date for a task at
    * the time
    * you saved a baseline. Information in this field becomes available when you
    * set a baseline.
    *
    * @param val Date to be set
    */
   public void setBaselineStart(Date val)
   {
      set(TaskField.BASELINE_START, val);
   }

   /**
    * The Baseline Work field shows the originally planned amount of work to
    * be performed
    * by all resources assigned to a task. This field shows the planned
    * person-hours
    * scheduled for a task. Information in the Baseline Work field
    * becomes available
    * when you set a baseline for the project.
    *
    * @param val the duration to be set.
    */
   public void setBaselineWork(Duration val)
   {
      set(TaskField.BASELINE_WORK, val);
   }

   /**
    * The BCWP (budgeted cost of work performed) field contains the
    * cumulative value
    * of the assignment's timephased percent complete multiplied by
    * the assignments
    * timephased baseline cost. BCWP is calculated up to the status
    * date or todays
    * date. This information is also known as earned value.
    *
    * @param val the amount to be set
    */
   public void setBCWP(Number val)
   {
      set(TaskField.BCWP, val);
   }

   /**
    * The BCWS (budgeted cost of work scheduled) field contains the cumulative
    * timephased baseline costs up to the status date or today's date.
    *
    * @param val the amount to set
    */
   public void setBCWS(Number val)
   {
      set(TaskField.BCWS, val);
   }

   /**
    * The Confirmed field indicates whether all resources assigned to a task have
    * accepted or rejected the task assignment in response to a TeamAssign message
    * regarding their assignments.
    *
    * @param val boolean value
    */
   public void setConfirmed(boolean val)
   {
      set(TaskField.CONFIRMED, val);
   }

   /**
    * The Constraint Date field shows the specific date associated with certain
    * constraint types,
    *  such as Must Start On, Must Finish On, Start No Earlier Than,
    *  Start No Later Than,
    *  Finish No Earlier Than, and Finish No Later Than.
    *  SEE class constants
    *
    * @param val Date to be set
    */
   public void setConstraintDate(Date val)
   {
      set(TaskField.CONSTRAINT_DATE, val);
   }

   /**
    * Set the secondary constraint date.
    *
    * @param date secondary constraint date
    */
   public void setSecondaryConstraintDate(Date date)
   {
      set(TaskField.SECONDARY_CONSTRAINT_DATE, date);
   }

   /**
    * Private method for dealing with string parameters from File.
    *
    * @param type string constraint type
    */
   public void setConstraintType(ConstraintType type)
   {
      set(TaskField.CONSTRAINT_TYPE, type);
   }

   /**
    * Set the secondary constraint type.
    *
    * @param type secondary constraint type
    */
   public void setSecondaryConstraintType(ConstraintType type)
   {
      set(TaskField.SECONDARY_CONSTRAINT_TYPE, type);
   }

   /**
    * The Contact field contains the name of an individual
    * responsible for a task.
    *
    * @param val value to be set
    */
   public void setContact(String val)
   {
      set(TaskField.CONTACT, val);
   }

   /**
    * The Cost field shows the total scheduled, or projected, cost for a task,
    * based on costs already incurred for work performed by all resources assigned
    * to the task, in addition to the costs planned for the remaining work for the
    * assignment. This can also be referred to as estimate at completion (EAC).
    *
    * @param val amount
    */
   public void setCost(Number val)
   {
      set(TaskField.COST, val);
   }

   /**
    * Set a cost value.
    *
    * @param index cost index (1-10)
    * @param value cost value
    */
   public void setCost(int index, Number value)
   {
      set(selectField(TaskFieldLists.CUSTOM_COST, index), value);
   }

   /**
    * Retrieve a cost value.
    *
    * @param index cost index (1-10)
    * @return cost value
    */
   public Number getCost(int index)
   {
      return (Number) getCachedValue(selectField(TaskFieldLists.CUSTOM_COST, index));
   }

   /**
    * The Cost Variance field shows the difference between the
    * baseline cost and total cost for a task. The total cost is the
    * current estimate of costs based on actual costs and remaining costs.
    *
    * @param val amount
    */
   public void setCostVariance(Number val)
   {
      set(TaskField.COST_VARIANCE, val);
   }

   /**
    * The Created field contains the date and time when a task was
    * added to the project.
    *
    * @param val date
    */
   public void setCreateDate(Date val)
   {
      set(TaskField.CREATED, val);
   }

   /**
    * The Critical field indicates whether a task has any room in the
    * schedule to slip,
    * or if a task is on the critical path. The Critical field contains
    * Yes if the task
    * is critical and No if the task is not critical.
    *
    * @param val whether task is critical or not
    */
   public void setCritical(boolean val)
   {
      set(TaskField.CRITICAL, val);
   }

   /**
    * The CV (earned value cost variance) field shows the difference
    * between how much it should have cost to achieve the current level of
    * completion on the task, and how much it has actually cost to achieve the
    * current level of completion up to the status date or today's date.
    *
    * @param val value to set
    */
   public void setCV(Number val)
   {
      set(TaskField.CV, val);
   }

   /**
    * Set amount of delay as elapsed real time.
    *
    * @param val elapsed time
    */
   public void setLevelingDelay(Duration val)
   {
      set(TaskField.LEVELING_DELAY, val);
   }

   /**
    * The Duration field is the total span of active working time for a task.
    * This is generally the amount of time from the start to the finish of a task.
    * The default for new tasks is 1 day (1d).
    *
    * @param val duration
    */
   public void setDuration(Duration val)
   {
      set(TaskField.DURATION, val);
   }

   /**
    * Set the duration text used for a manually scheduled task.
    *
    * @param val text
    */
   public void setDurationText(String val)
   {
      set(TaskField.DURATION_TEXT, val);
   }

   /**
    * Set the manual duration attribute.
    *
    * @param dur manual duration
    */
   public void setManualDuration(Duration dur)
   {
      set(TaskField.MANUAL_DURATION, dur);
   }

   /**
    * Read the manual duration attribute.
    *
    * @return manual duration
    */
   public Duration getManualDuration()
   {
      return (Duration) getCachedValue(TaskField.MANUAL_DURATION);
   }

   /**
    * The Duration Variance field contains the difference between the
    * baseline duration of a task and the forecast or actual duration
    * of the task.
    *
    * @param duration duration value
    */
   public void setDurationVariance(Duration duration)
   {
      set(TaskField.DURATION_VARIANCE, duration);
   }

   /**
    * The Early Finish field contains the earliest date that a task
    * could possibly finish, based on early finish dates of predecessor
    * and successor tasks, other constraints, and any leveling delay.
    *
    * @param date Date value
    */
   public void setEarlyFinish(Date date)
   {
      set(TaskField.EARLY_FINISH, date);
   }

   /**
   * The date the resource is scheduled to finish the remaining work for the activity.
   *
   * @param date Date value
   */
   public void setRemainingEarlyFinish(Date date)
   {
      set(TaskField.REMAINING_EARLY_FINISH, date);
   }

   /**
    * The Early Start field contains the earliest date that a task could
    * possibly begin, based on the early start dates of predecessor and
    * successor tasks, and other constraints.
    *
    * @param date Date value
    */
   public void setEarlyStart(Date date)
   {
      set(TaskField.EARLY_START, date);
   }

   /**
   * The date the resource is scheduled to begin the remaining work for the activity.
   *
   * @param date Date value
   */
   public void setRemainingEarlyStart(Date date)
   {
      set(TaskField.REMAINING_EARLY_START, date);
   }

   /**
    * The Finish field shows the date and time that a task is scheduled to be
    * completed. MS project allows a finish date to be entered, and will
    * calculate the duration, or a duration can be supplied and MS Project
    * will calculate the finish date.
    *
    * @param date Date value
    */
   public void setFinish(Date date)
   {
      set(TaskField.FINISH, date);
   }

   /**
    * Set the finish text used for a manually scheduled task.
    *
    * @param val text
    */
   public void setFinishText(String val)
   {
      set(TaskField.FINISH_TEXT, val);
   }

   /**
    * The Finish Variance field contains the amount of time that represents the
    * difference between a task's baseline finish date and its forecast
    * or actual finish date.
    *
    * @param duration duration value
    */
   public void setFinishVariance(Duration duration)
   {
      set(TaskField.FINISH_VARIANCE, duration);
   }

   /**
    * The Fixed Cost field shows any task expense that is not associated
    * with a resource cost.
    *
    * @param val amount
    */
   public void setFixedCost(Number val)
   {
      set(TaskField.FIXED_COST, val);
   }

   /**
    * The Free Slack field contains the amount of time that a task can be
    * delayed without delaying any successor tasks. If the task has no
    * successors, free slack is the amount of time that a task can be delayed
    * without delaying the entire project's finish date.
    *
    * @param duration duration value
    */
   public void setFreeSlack(Duration duration)
   {
      set(TaskField.FREE_SLACK, duration);
   }

   /**
    * The Hide Bar flag indicates whether the Gantt bars and Calendar bars
    * for a task are hidden when this project's data is displayed in MS Project.
    *
    * @param flag boolean value
    */
   public void setHideBar(boolean flag)
   {
      set(TaskField.HIDE_BAR, flag);
   }

   /**
    * The ID field contains the identifier number that Microsoft Project
    * automatically assigns to each task as you add it to the project.
    * The ID indicates the position of a task with respect to the other tasks.
    *
    * @param val ID
    */
   @Override public void setID(Integer val)
   {
      ProjectFile parent = getParentFile();
      Integer previous = getID();

      if (previous != null)
      {
         parent.getTasks().unmapID(previous);
      }

      parent.getTasks().mapID(val, this);

      set(TaskField.ID, val);
   }

   /**
    * The Late Finish field contains the latest date that a task can finish
    * without delaying the finish of the project. This date is based on the
    * task's late start date, as well as the late start and late finish dates
    * of predecessor and successor tasks, and other constraints.
    *
    * @param date date value
    */
   public void setLateFinish(Date date)
   {
      set(TaskField.LATE_FINISH, date);
   }

   /**
    * The Late Start field contains the latest date that a task can start
    * without delaying the finish of the project. This date is based on the
    * task's start date, as well as the late start and late finish dates of
    * predecessor and successor tasks, and other constraints.
    *
    * @param date date value
    */
   public void setLateStart(Date date)
   {
      set(TaskField.LATE_START, date);
   }

   /**
    * The Linked Fields field indicates whether there are OLE links to the task,
    * either from elsewhere in the active project, another Microsoft Project
    * file, or from another program.
    *
    * @param flag boolean value
    */
   public void setLinkedFields(boolean flag)
   {
      set(TaskField.LINKED_FIELDS, flag);
   }

   /**
    * This is a user defined field used to mark a task for some form of
    * additional action.
    *
    * @param flag boolean value
    */
   public void setMarked(boolean flag)
   {
      set(TaskField.MARKED, flag);
   }

   /**
    * The Milestone field indicates whether a task is a milestone.
    *
    * @param flag boolean value
    */
   public void setMilestone(boolean flag)
   {
      set(TaskField.MILESTONE, flag);
   }

   /**
    * The Name field contains the name of a task.
    *
    * @param name task name
    */
   public void setName(String name)
   {
      set(TaskField.NAME, name);
   }

   /**
    * The Objects field contains the number of objects attached to a task.
    *
    * @param val - integer value
    */
   public void setObjects(Integer val)
   {
      set(TaskField.OBJECTS, val);
   }

   /**
    * The Outline Level field contains the number that indicates the level of
    * the task in the project outline hierarchy.
    *
    * @param val - int
    */
   public void setOutlineLevel(Integer val)
   {
      set(TaskField.OUTLINE_LEVEL, val);
   }

   /**
    * The Outline Number field contains the number of the task in the structure
    * of an outline. This number indicates the task's position within the
    * hierarchical structure of the project outline. The outline number is
    * similar to a WBS (work breakdown structure) number, except that the
    * outline number is automatically entered by Microsoft Project.
    *
    * @param val - text
    */
   public void setOutlineNumber(String val)
   {
      set(TaskField.OUTLINE_NUMBER, val);
   }

   /**
    * The Priority field provides choices for the level of importance
    * assigned to a task, which in turn indicates how readily a task can be
    * delayed or split during resource leveling.
    * The default priority is Medium. Those tasks with a priority
    * of Do Not Level are never delayed or split when Microsoft Project levels
    * tasks that have overallocated resources assigned.
    *
    * @param priority the priority value
    */
   public void setPriority(Priority priority)
   {
      set(TaskField.PRIORITY, priority);
   }

   /**
    * The Project field shows the name of the project from which a
    * task originated.
    * This can be the name of the active project file. If there are
    * other projects
    * inserted into the active project file, the name of the
    * inserted project appears
    * in this field for the task.
    *
    * @param val - text
    */
   public void setProject(String val)
   {
      set(TaskField.PROJECT, val);
   }

   /**
    * The Remaining Cost field shows the remaining scheduled expense of a task that
    * will be incurred in completing the remaining scheduled work by all resources
    * assigned to the task.
    *
    * @param val - currency amount
    */
   public void setRemainingCost(Number val)
   {
      set(TaskField.REMAINING_COST, val);
   }

   /**
    * The Remaining Duration field shows the amount of time required to complete
    * the unfinished portion of a task.
    *
    * @param val - duration.
    */
   public void setRemainingDuration(Duration val)
   {
      set(TaskField.REMAINING_DURATION, val);
   }

   /**
    * The Remaining Work field shows the amount of time, or person-hours,
    * still required by all assigned resources to complete a task.
    * @param val  - duration
    */
   public void setRemainingWork(Duration val)
   {
      set(TaskField.REMAINING_WORK, val);
   }

   /**
    * The Resource Group field contains the list of resource groups to which the
    * resources assigned to a task belong.
    *
    * @param val - String list
    */
   public void setResourceGroup(String val)
   {
      set(TaskField.RESOURCE_GROUP, val);
   }

   /**
    * The Resource Initials field lists the abbreviations for the names of
    * resources assigned to a task. These initials can serve as substitutes
    * for the names.
    *
    * Note that MS Project 98 does no normally populate this field when
    * it generates an MPX file, and will therefore not expect to see values
    * in this field when it reads an MPX file. Supplying values for this
    * field will cause MS Project 98, 2000, and 2002 to create new resources
    * and ignore any other resource assignments that have been defined
    * in the MPX file.
    *
    * @param val String containing a comma separated list of initials
    */
   public void setResourceInitials(String val)
   {
      set(TaskField.RESOURCE_INITIALS, val);
   }

   /**
    * The Resource Names field lists the names of all resources
    * assigned to a task.
    *
    * Note that MS Project 98 does not normally populate this field when
    * it generates an MPX file, and will therefore not expect to see values
    * in this field when it reads an MPX file. Supplying values for this
    * field when writing an MPX file will cause MS Project 98, 2000, and 2002
    * to create new resources and ignore any other resource assignments
    * that have been defined in the MPX file.
    *
    * @param val String containing a comma separated list of names
    */
   public void setResourceNames(String val)
   {
      set(TaskField.RESOURCE_NAMES, val);
   }

   /**
    * The Resume field shows the date that the remaining portion of a task is
    * scheduled to resume after you enter a new value for the % Complete field.
    * The Resume field is also recalculated when the remaining portion of a task
    * is moved to a new date.
    *
    * @param val - Date
    */
   public void setResume(Date val)
   {
      set(TaskField.RESUME, val);
   }

   /**
    * For subtasks, the Rollup field indicates whether information on the subtask
    * Gantt bars will be rolled up to the summary task bar. For summary tasks, the
    * Rollup field indicates whether the summary task bar displays rolled up bars.
    * You must have the Rollup field for summary tasks set to Yes for any subtasks
    * to roll up to them.
    *
    * @param val - boolean
    */
   public void setRollup(boolean val)
   {
      set(TaskField.ROLLUP, val);
   }

   /**
    * The Start field shows the date and time that a task is scheduled to begin.
    * You can enter the start date you want, to indicate the date when the task
    * should begin. Or, you can have Microsoft Project calculate the start date.
    * @param val - Date
    */
   public void setStart(Date val)
   {
      set(TaskField.START, val);
   }

   /**
    * Set the start text used for a manually scheduled task.
    *
    * @param val text
    */
   public void setStartText(String val)
   {
      set(TaskField.START_TEXT, val);
   }

   /**
    * The Start Variance field contains the amount of time that represents the
    * difference between a task's baseline start date and its currently
    * scheduled start date.
    *
    * @param val - duration
    */
   public void setStartVariance(Duration val)
   {
      set(TaskField.START_VARIANCE, val);
   }

   /**
    * The Stop field shows the date that represents the end of the actual
    * portion of a task. Typically, Microsoft Project calculates the stop date.
    * However, you can edit this date as well.
    *
    * @param val - Date
    */
   public void setStop(Date val)
   {
      set(TaskField.STOP, val);
   }

   /**
    * The Subproject File field contains the name of a project inserted into
    * the active project file. The Subproject File field contains the inserted
    * project's path and file name.
    *
    * @param val - String
    */
   public void setSubprojectName(String val)
   {
      set(TaskField.SUBPROJECT_FILE, val);
   }

   /**
    * The Summary field indicates whether a task is a summary task.
    *
    * @param val - boolean
    */
   public void setSummary(boolean val)
   {
      set(TaskField.SUMMARY, val);
   }

   /**
    * The SV (earned value schedule variance) field shows the difference
    * in cost terms between the current progress and the baseline plan
    * of the task up to the status date or today's date. You can use SV
    * to check costs to determine whether tasks are on schedule.
    * @param val - currency amount
    */
   public void setSV(Number val)
   {
      set(TaskField.SV, val);
   }

   /**
    * The Total Slack field contains the amount of time a task can be delayed
    * without delaying the project's finish date.
    *
    * @param val - duration
    */
   public void setTotalSlack(Duration val)
   {
      set(TaskField.TOTAL_SLACK, val);
   }

   /**
    * The Unique ID field contains the number that Microsoft Project
    * automatically designates whenever a new task is created.
    * This number indicates the sequence in which the task was created,
    * regardless of placement in the schedule.
    *
    * @param val unique ID
    */
   @Override public void setUniqueID(Integer val)
   {
      set(TaskField.UNIQUE_ID, val);
   }

   /**
    * The Update Needed field indicates whether a TeamUpdate message should
    * be sent to the assigned resources because of changes to the start date,
    * finish date, or resource reassignments of the task.
    *
    * @param val - boolean
    */
   public void setUpdateNeeded(boolean val)
   {
      set(TaskField.UPDATE_NEEDED, val);
   }

   /**
    * The work breakdown structure code. The WBS field contains an alphanumeric
    * code you can use to represent the task's position within the hierarchical
    * structure of the project. This field is similar to the outline number,
    * except that you can edit it.
    *
    * @param val - String
    */
   public void setWBS(String val)
   {
      set(TaskField.WBS, val);
   }

   /**
    * The Work field shows the total amount of work scheduled to be performed
    * on a task by all assigned resources. This field shows the total work,
    * or person-hours, for a task.
    *
    * @param val - duration
    */
   public void setWork(Duration val)
   {
      set(TaskField.WORK, val);
   }

   /**
    * The Work Variance field contains the difference between a task's baseline
    * work and the currently scheduled work.
    *
    * @param val - duration
    */
   public void setWorkVariance(Duration val)
   {
      set(TaskField.WORK_VARIANCE, val);
   }

   /**
    * The % Complete field contains the current status of a task,
    * expressed as the percentage of the task's duration that has been completed.
    * You can enter percent complete, or you can have Microsoft Project calculate
    * it for you based on actual duration.
    * @return percentage as float
    */
   public Number getPercentageComplete()
   {
      return ((Number) getCachedValue(TaskField.PERCENT_COMPLETE));
   }

   /**
    * The % Work Complete field contains the current status of a task,
    * expressed as the percentage of the task's work that has been completed.
    * You can enter percent work complete, or you can have Microsoft Project
    * calculate it for you based on actual work on the task.
    *
    * @return percentage as float
    */
   public Number getPercentageWorkComplete()
   {
      return ((Number) getCachedValue(TaskField.PERCENT_WORK_COMPLETE));
   }

   /**
    * The Actual Cost field shows costs incurred for work already performed
    * by all resources on a task, along with any other recorded costs associated
    * with the task. You can enter all the actual costs or have Microsoft Project
    * calculate them for you.
    *
    * @return currency amount as float
    */
   public Number getActualCost()
   {
      return ((Number) getCachedValue(TaskField.ACTUAL_COST));
   }

   /**
    * The Actual Duration field shows the span of actual working time for a
    * task so far, based on the scheduled duration and current remaining work
    * or completion percentage.
    *
    * @return duration string
    */
   public Duration getActualDuration()
   {
      return ((Duration) getCachedValue(TaskField.ACTUAL_DURATION));
   }

   /**
    * The Actual Finish field shows the date and time that a task actually
    * finished. Microsoft Project sets the Actual Finish field to the scheduled
    * finish date if the completion percentage is 100. This field contains "NA"
    * until you enter actual information or set the completion percentage to 100.
    * If "NA" is entered as value, arbitrary year zero Date is used. Date(0);
    *
    * @return Date
    */
   public Date getActualFinish()
   {
      return ((Date) getCachedValue(TaskField.ACTUAL_FINISH));
   }

   /**
    * The Actual Start field shows the date and time that a task actually began.
    * When a task is first created, the Actual Start field contains "NA." Once
    * you enter the first actual work or a completion percentage for a task,
    * Microsoft Project sets the actual start date to the scheduled start date.
    * If "NA" is entered as value, arbitrary year zero Date is used. Date(0);
    *
    * @return Date
    */
   public Date getActualStart()
   {
      return ((Date) getCachedValue(TaskField.ACTUAL_START));
   }

   /**
    * The Actual Work field shows the amount of work that has already been done
    * by the resources assigned to a task.
    *
    * @return duration string
    */
   public Duration getActualWork()
   {
      return ((Duration) getCachedValue(TaskField.ACTUAL_WORK));
   }

   /**
    * The Baseline Cost field shows the total planned cost for a task.
    * Baseline cost is also referred to as budget at completion (BAC).
    * @return currency amount as float
    */
   public Number getBaselineCost()
   {
      return ((Number) getCachedValue(TaskField.BASELINE_COST));
   }

   /**
    * The Baseline Duration field shows the original span of time planned
    * to complete a task.
    *
    * @return  - duration string
    */
   public Duration getBaselineDuration()
   {
      Object result = getCachedValue(TaskField.BASELINE_DURATION);
      if (result == null)
      {
         result = getCachedValue(TaskField.BASELINE_ESTIMATED_DURATION);
      }

      if (!(result instanceof Duration))
      {
         result = null;
      }
      return (Duration) result;
   }

   /**
    * Retrieves the text value for the baseline duration.
    *
    * @return baseline duration text
    */
   public String getBaselineDurationText()
   {
      Object result = getCachedValue(TaskField.BASELINE_DURATION);
      if (result == null)
      {
         result = getCachedValue(TaskField.BASELINE_ESTIMATED_DURATION);
      }

      if (!(result instanceof String))
      {
         result = null;
      }
      return (String) result;
   }

   /**
    * Sets the baseline duration text value.
    *
    * @param value baseline duration text
    */
   public void setBaselineDurationText(String value)
   {
      set(TaskField.BASELINE_DURATION, value);
   }

   /**
    * The Baseline Finish field shows the planned completion date for a task
    * at the time you saved a baseline. Information in this field becomes
    * available when you set a baseline for a task.
    *
    * @return Date
    */
   public Date getBaselineFinish()
   {
      Object result = getCachedValue(TaskField.BASELINE_FINISH);
      if (result == null)
      {
         result = getCachedValue(TaskField.BASELINE_ESTIMATED_FINISH);
      }

      if (!(result instanceof Date))
      {
         result = null;
      }
      return (Date) result;
   }

   /**
    * Retrieves the baseline finish text value.
    *
    * @return baseline finish text
    */
   public String getBaselineFinishText()
   {
      Object result = getCachedValue(TaskField.BASELINE_FINISH);
      if (result == null)
      {
         result = getCachedValue(TaskField.BASELINE_ESTIMATED_FINISH);
      }

      if (!(result instanceof String))
      {
         result = null;
      }
      return (String) result;
   }

   /**
    * Sets the baseline finish text value.
    *
    * @param value baseline finish text
    */
   public void setBaselineFinishText(String value)
   {
      set(TaskField.BASELINE_FINISH, value);
   }

   /**
    * The Baseline Start field shows the planned beginning date for a task at
    * the time you saved a baseline. Information in this field becomes available
    * when you set a baseline.
    *
    * @return Date
    */
   public Date getBaselineStart()
   {
      Object result = getCachedValue(TaskField.BASELINE_START);
      if (result == null)
      {
         result = getCachedValue(TaskField.BASELINE_ESTIMATED_START);
      }

      if (!(result instanceof Date))
      {
         result = null;
      }
      return (Date) result;
   }

   /**
    * Retrieves the baseline start text value.
    *
    * @return baseline start value
    */
   public String getBaselineStartText()
   {
      Object result = getCachedValue(TaskField.BASELINE_START);
      if (result == null)
      {
         result = getCachedValue(TaskField.BASELINE_ESTIMATED_START);
      }

      if (!(result instanceof String))
      {
         result = null;
      }
      return (String) result;
   }

   /**
    * Sets the baseline start text value.
    *
    * @param value baseline start text
    */
   public void setBaselineStartText(String value)
   {
      set(TaskField.BASELINE_START, value);
   }

   /**
    * The Baseline Work field shows the originally planned amount of work to be
    * performed by all resources assigned to a task. This field shows the planned
    * person-hours scheduled for a task. Information in the Baseline Work field
    * becomes available when you set a baseline for the project.
    *
    * @return Duration
    */
   public Duration getBaselineWork()
   {
      return ((Duration) getCachedValue(TaskField.BASELINE_WORK));
   }

   /**
    * The BCWP (budgeted cost of work performed) field contains
    * the cumulative value of the assignment's timephased percent complete
    * multiplied by the assignment's timephased baseline cost.
    * BCWP is calculated up to the status date or today's date.
    * This information is also known as earned value.
    *
    * @return currency amount as float
    */
   public Number getBCWP()
   {
      return ((Number) getCachedValue(TaskField.BCWP));
   }

   /**
    * The BCWS (budgeted cost of work scheduled) field contains the cumulative
    * timephased baseline costs up to the status date or today's date.
    *
    * @return currency amount as float
    */
   public Number getBCWS()
   {
      return ((Number) getCachedValue(TaskField.BCWS));
   }

   /**
    * The Confirmed field indicates whether all resources assigned to a task
    * have accepted or rejected the task assignment in response to a TeamAssign
    * message regarding their assignments.
    *
    * @return boolean
    */
   public boolean getConfirmed()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.CONFIRMED)));
   }

   /**
    * The Constraint Date field shows the specific date associated with certain
    * constraint types, such as Must Start On, Must Finish On,
    * Start No Earlier Than,
    * Start No Later Than, Finish No Earlier Than, and Finish No Later Than.
    *
    * @return Date
    */
   public Date getConstraintDate()
   {
      return ((Date) getCachedValue(TaskField.CONSTRAINT_DATE));
   }

   /**
    * Retrieve the secondary constraint date.
    *
    * @return secondary constraint date
    */
   public Date getSecondaryConstraintDate()
   {
      return ((Date) getCachedValue(TaskField.SECONDARY_CONSTRAINT_DATE));
   }

   /**
    * The Constraint Type field provides choices for the type of constraint you
    * can apply for scheduling a task.
    *
    * @return constraint type
    */
   public ConstraintType getConstraintType()
   {
      return ((ConstraintType) getCachedValue(TaskField.CONSTRAINT_TYPE));
   }

   /**
    * Retrieve the secondary constraint type.
    *
    * @return secondary constraint type
    */
   public ConstraintType getSecondaryConstraintType()
   {
      return ((ConstraintType) getCachedValue(TaskField.SECONDARY_CONSTRAINT_TYPE));
   }

   /**
    * The Contact field contains the name of an individual
    * responsible for a task.
    *
    * @return String
    */
   public String getContact()
   {
      return ((String) getCachedValue(TaskField.CONTACT));
   }

   /**
    * The Cost field shows the total scheduled, or projected, cost for a task,
    * based on costs already incurred for work performed by all resources assigned
    * to the task, in addition to the costs planned for the remaining work for the
    * assignment. This can also be referred to as estimate at completion (EAC).
    *
    * @return cost amount
    */
   public Number getCost()
   {
      return ((Number) getCachedValue(TaskField.COST));
   }

   /**
    * The Cost Variance field shows the difference between the baseline cost
    * and total cost for a task. The total cost is the current estimate of costs
    * based on actual costs and remaining costs.
    *
    * @return amount
    */
   public Number getCostVariance()
   {
      Number variance = (Number) getCachedValue(TaskField.COST_VARIANCE);
      if (variance == null)
      {
         Number cost = getCost();
         Number baselineCost = getBaselineCost();
         if (cost != null && baselineCost != null)
         {
            variance = NumberHelper.getDouble(cost.doubleValue() - baselineCost.doubleValue());
            set(TaskField.COST_VARIANCE, variance);
         }
      }
      return (variance);
   }

   /**
    * The Created field contains the date and time when a task was added
    * to the project.
    *
    * @return Date
    */
   public Date getCreateDate()
   {
      return ((Date) getCachedValue(TaskField.CREATED));
   }

   /**
    * The Critical field indicates whether a task has any room in the schedule
    * to slip, or if a task is on the critical path. The Critical field contains
    * Yes if the task is critical and No if the task is not critical.
    *
    * @return boolean
    */
   public boolean getCritical()
   {
      Boolean critical = (Boolean) getCachedValue(TaskField.CRITICAL);
      if (critical == null)
      {
         Duration totalSlack = getTotalSlack();
         ProjectProperties props = getParentFile().getProjectProperties();
         int criticalSlackLimit = NumberHelper.getInt(props.getCriticalSlackLimit());
         if (criticalSlackLimit != 0 && totalSlack.getDuration() != 0 && totalSlack.getUnits() != TimeUnit.DAYS)
         {
            totalSlack = totalSlack.convertUnits(TimeUnit.DAYS, props);
         }
         critical = Boolean.valueOf(totalSlack.getDuration() <= criticalSlackLimit && NumberHelper.getInt(getPercentageComplete()) != 100 && ((getTaskMode() == TaskMode.AUTO_SCHEDULED) || (getDurationText() == null && getStartText() == null && getFinishText() == null)));
         set(TaskField.CRITICAL, critical);
      }
      return (BooleanHelper.getBoolean(critical));
   }

   /**
    * The CV (earned value cost variance) field shows the difference between
    * how much it should have cost to achieve the current level of completion
    * on the task, and how much it has actually cost to achieve the current
    * level of completion up to the status date or today's date.
    * How Calculated   CV is the difference between BCWP
    * (budgeted cost of work performed) and ACWP
    * (actual cost of work performed). Microsoft Project calculates
    * the CV as follows: CV = BCWP - ACWP
    *
    * @return sum of earned value cost variance
    */
   public Number getCV()
   {
      Number variance = (Number) getCachedValue(TaskField.CV);
      if (variance == null)
      {
         variance = Double.valueOf(NumberHelper.getDouble(getBCWP()) - NumberHelper.getDouble(getACWP()));
         set(TaskField.CV, variance);
      }
      return (variance);
   }

   /**
    * Delay , in MPX files as eg '0ed'. Use duration
    *
    * @return Duration
    */
   public Duration getLevelingDelay()
   {
      return ((Duration) getCachedValue(TaskField.LEVELING_DELAY));
   }

   /**
    * The Duration field is the total span of active working time for a task.
    * This is generally the amount of time from the start to the finish of a task.
    * The default for new tasks is 1 day (1d).
    *
    * @return Duration
    */
   public Duration getDuration()
   {
      return (Duration) getCachedValue(TaskField.DURATION);
   }

   /**
    * Retrieves the duration text of a manually scheduled task.
    *
    * @return duration text
    */
   public String getDurationText()
   {
      return (String) getCachedValue(TaskField.DURATION_TEXT);
   }

   /**
    * Set a duration value.
    *
    * @param index duration index (1-10)
    * @param value duration value
    */
   public void setDuration(int index, Duration value)
   {
      set(selectField(TaskFieldLists.CUSTOM_DURATION, index), value);
   }

   /**
    * Retrieve a duration value.
    *
    * @param index duration index (1-10)
    * @return duration value
    */
   public Duration getDuration(int index)
   {
      return (Duration) getCachedValue(selectField(TaskFieldLists.CUSTOM_DURATION, index));
   }

   /**
    * The Duration Variance field contains the difference between the
    * baseline duration of a task and the total duration (current estimate)
    * of a task.
    *
    * @return Duration
    */
   public Duration getDurationVariance()
   {
      Duration variance = (Duration) getCachedValue(TaskField.DURATION_VARIANCE);
      if (variance == null)
      {
         Duration duration = getDuration();
         Duration baselineDuration = getBaselineDuration();

         if (duration != null && baselineDuration != null)
         {
            variance = Duration.getInstance(duration.getDuration() - baselineDuration.convertUnits(duration.getUnits(), getParentFile().getProjectProperties()).getDuration(), duration.getUnits());
            set(TaskField.DURATION_VARIANCE, variance);
         }
      }
      return (variance);
   }

   /**
    * The Early Finish field contains the earliest date that a task could
    * possibly finish, based on early finish dates of predecessor and
    * successor tasks, other constraints, and any leveling delay.
    *
    * @return Date
    */
   public Date getEarlyFinish()
   {
      return ((Date) getCachedValue(TaskField.EARLY_FINISH));
   }

   /**
   * The date the resource is scheduled to finish the remaining work for the activity.
   *
   * @return Date
   */
   public Date getRemainingEarlyFinish()
   {
      return ((Date) getCachedValue(TaskField.REMAINING_EARLY_FINISH));
   }

   /**
    * The Early Start field contains the earliest date that a task could
    * possibly begin, based on the early start dates of predecessor and
    * successor tasks, and other constraints.
    *
    * @return Date
    */
   public Date getEarlyStart()
   {
      return ((Date) getCachedValue(TaskField.EARLY_START));
   }

   /**
   * The date the resource is scheduled to start the remaining work for the activity.
   *
   * @return Date
   */
   public Date getRemainingEarlyStart()
   {
      return ((Date) getCachedValue(TaskField.REMAINING_EARLY_START));
   }

   /**
    * The Finish field shows the date and time that a task is scheduled to
    * be completed. You can enter the finish date you want, to indicate the
    * date when the task should be completed. Or, you can have Microsoft
    * Project calculate the finish date.
    *
    * @return Date
    */
   public Date getFinish()
   {
      return (Date) getCachedValue(TaskField.FINISH);
   }

   /**
    * Retrieves the finish text of a manually scheduled task.
    *
    * @return finish text
    */
   public String getFinishText()
   {
      return (String) getCachedValue(TaskField.FINISH_TEXT);
   }

   /**
    * Set a finish value.
    *
    * @param index finish index (1-10)
    * @param value finish value
    */
   public void setFinish(int index, Date value)
   {
      set(selectField(TaskFieldLists.CUSTOM_FINISH, index), value);
   }

   /**
    * Retrieve a finish value.
    *
    * @param index finish index (1-10)
    * @return finish value
    */
   public Date getFinish(int index)
   {
      return (Date) getCachedValue(selectField(TaskFieldLists.CUSTOM_FINISH, index));
   }

   /**
    * Calculate the finish variance.
    *
    * @return finish variance
    */
   public Duration getFinishVariance()
   {
      Duration variance = (Duration) getCachedValue(TaskField.FINISH_VARIANCE);
      if (variance == null)
      {
         TimeUnit format = getParentFile().getProjectProperties().getDefaultDurationUnits();
         variance = DateHelper.getVariance(this, getBaselineFinish(), getFinish(), format);
         set(TaskField.FINISH_VARIANCE, variance);
      }
      return (variance);
   }

   /**
    * The Fixed Cost field shows any task expense that is not associated
    * with a resource cost.
    *
    * @return currency amount
    */
   public Number getFixedCost()
   {
      return ((Number) getCachedValue(TaskField.FIXED_COST));
   }

   /**
    * Set a flag value.
    *
    * @param index flag index (1-20)
    * @param value flag value
    */
   public void setFlag(int index, boolean value)
   {
      set(selectField(TaskFieldLists.CUSTOM_FLAG, index), value);
   }

   /**
    * Retrieve a flag value.
    *
    * @param index flag index (1-20)
    * @return flag value
    */
   public boolean getFlag(int index)
   {
      return BooleanHelper.getBoolean((Boolean) getCachedValue(selectField(TaskFieldLists.CUSTOM_FLAG, index)));
   }

   /**
    * The Free Slack field contains the amount of time that a task can be
    * delayed without delaying any successor tasks. If the task has no
    * successors, free slack is the amount of time that a task can be
    * delayed without delaying the entire project's finish date.
    *
    * @return Duration
    */
   public Duration getFreeSlack()
   {
      return ((Duration) getCachedValue(TaskField.FREE_SLACK));
   }

   /**
    * The Hide Bar field indicates whether the Gantt bars and Calendar bars
    * for a task are hidden. Click Yes in the Hide Bar field to hide the
    * bar for the task. Click No in the Hide Bar field to show the bar
    * for the task.
    *
    * @return boolean
    */
   public boolean getHideBar()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.HIDE_BAR)));
   }

   /**
    * The ID field contains the identifier number that Microsoft Project
    * automatically assigns to each task as you add it to the project.
    * The ID indicates the position of a task with respect to the other tasks.
    *
    * @return the task ID
    */
   @Override public Integer getID()
   {
      return ((Integer) getCachedValue(TaskField.ID));
   }

   /**
    * The Late Finish field contains the latest date that a task can finish
    * without delaying the finish of the project. This date is based on the
    * task's late start date, as well as the late start and late finish
    * dates of predecessor and successor
    * tasks, and other constraints.
    *
    * @return Date
    */
   public Date getLateFinish()
   {
      return ((Date) getCachedValue(TaskField.LATE_FINISH));
   }

   /**
    * The Late Start field contains the latest date that a task can start
    * without delaying the finish of the project. This date is based on
    * the task's start date, as well as the late start and late finish
    * dates of predecessor and successor tasks, and other constraints.
    *
    * @return Date
    */
   public Date getLateStart()
   {
      return ((Date) getCachedValue(TaskField.LATE_START));
   }

   /**
    * The Linked Fields field indicates whether there are OLE links to the task,
    * either from elsewhere in the active project, another Microsoft Project file,
    * or from another program.
    *
    * @return boolean
    */
   public boolean getLinkedFields()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.LINKED_FIELDS)));
   }

   /**
    * The Marked field indicates whether a task is marked for further action or
    * identification of some kind. To mark a task, click Yes in the Marked field.
    * If you don't want a task marked, click No.
    *
    * @return true for marked
    */
   public boolean getMarked()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.MARKED)));
   }

   /**
    * The Milestone field indicates whether a task is a milestone.
    *
    * @return boolean
    */
   public boolean getMilestone()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.MILESTONE)));
   }

   /**
    * Retrieves the task name.
    *
    * @return task name
    */
   public String getName()
   {
      return ((String) getCachedValue(TaskField.NAME));
   }

   /**
    * The Notes field contains notes that you can enter about a task.
    * You can use task notes to help maintain a history for a task.
    *
    * @return notes
    */
   public String getNotes()
   {
      String notes = (String) getCachedValue(TaskField.NOTES);
      return (notes == null ? "" : notes);
   }

   /**
    * Set a number value.
    *
    * @param index number index (1-20)
    * @param value number value
    */
   public void setNumber(int index, Number value)
   {
      set(selectField(TaskFieldLists.CUSTOM_NUMBER, index), value);
   }

   /**
    * Retrieve a number value.
    *
    * @param index number index (1-20)
    * @return number value
    */
   public Number getNumber(int index)
   {
      return (Number) getCachedValue(selectField(TaskFieldLists.CUSTOM_NUMBER, index));
   }

   /**
    * The Objects field contains the number of objects attached to a task.
    * Microsoft Project counts the number of objects linked or embedded to a task.
    * However, objects in the Notes box in the Resource Form are not included
    * in this count.
    *
    * @return int
    */
   public Integer getObjects()
   {
      return ((Integer) getCachedValue(TaskField.OBJECTS));
   }

   /**
    * The Outline Level field contains the number that indicates the level
    * of the task in the project outline hierarchy.
    *
    * @return int
    */
   public Integer getOutlineLevel()
   {
      return ((Integer) getCachedValue(TaskField.OUTLINE_LEVEL));
   }

   /**
    * The Outline Number field contains the number of the task in the structure
    * of an outline. This number indicates the task's position within the
    * hierarchical structure of the project outline. The outline number is
    * similar to a WBS (work breakdown structure) number,
    * except that the outline number is automatically entered by
    * Microsoft Project.
    *
    * @return String
    */
   public String getOutlineNumber()
   {
      return ((String) getCachedValue(TaskField.OUTLINE_NUMBER));
   }

   /**
    * Retrieves the list of predecessors for this task.
    *
    * @return list of predecessor Relation instances
    */
   @SuppressWarnings("unchecked") public List<Relation> getPredecessors()
   {
      return ((List<Relation>) getCachedValue(TaskField.PREDECESSORS));
   }

   /**
    * Retrieves the list of successors for this task.
    *
    * @return list of successor Relation instances
    */
   @SuppressWarnings("unchecked") public List<Relation> getSuccessors()
   {
      return ((List<Relation>) getCachedValue(TaskField.SUCCESSORS));
   }

   /**
    * The Priority field provides choices for the level of importance
    * assigned to a task, which in turn indicates how readily a task can be
    * delayed or split during resource leveling.
    * The default priority is Medium. Those tasks with a priority
    * of Do Not Level are never delayed or split when Microsoft Project levels
    * tasks that have overallocated resources assigned.
    *
    * @return priority class instance
    */
   public Priority getPriority()
   {
      return ((Priority) getCachedValue(TaskField.PRIORITY));
   }

   /**
    * The Project field shows the name of the project from which a task
    * originated.
    * This can be the name of the active project file. If there are other
    * projects inserted
    * into the active project file, the name of the inserted project appears
    * in this field
    * for the task.
    *
    * @return name of originating project
    */
   public String getProject()
   {
      return ((String) getCachedValue(TaskField.PROJECT));
   }

   /**
    * The Remaining Cost field shows the remaining scheduled expense of a
    * task that will be incurred in completing the remaining scheduled work
    * by all resources assigned to the task.
    *
    * @return remaining cost
    */
   public Number getRemainingCost()
   {
      return ((Number) getCachedValue(TaskField.REMAINING_COST));
   }

   /**
    * The Remaining Duration field shows the amount of time required
    * to complete the unfinished portion of a task.
    *
    * @return Duration
    */
   public Duration getRemainingDuration()
   {
      return ((Duration) getCachedValue(TaskField.REMAINING_DURATION));
   }

   /**
    * The Remaining Work field shows the amount of time, or person-hours,
    * still required by all assigned resources to complete a task.
    *
    * @return the amount of time still required to complete a task
    */
   public Duration getRemainingWork()
   {
      return ((Duration) getCachedValue(TaskField.REMAINING_WORK));
   }

   /**
    * The Resource Group field contains the list of resource groups to which
    * the resources assigned to a task belong.
    *
    * @return single string list of groups
    */
   public String getResourceGroup()
   {
      return ((String) getCachedValue(TaskField.RESOURCE_GROUP));
   }

   /**
    * The Resource Initials field lists the abbreviations for the names of
    * resources assigned to a task. These initials can serve as substitutes
    * for the names.
    *
    * Note that MS Project 98 does not export values for this field when
    * writing an MPX file, and the field is not currently populated by MPXJ
    * when reading an MPP file.
    *
    * @return String containing a comma separated list of initials
    */
   public String getResourceInitials()
   {
      return ((String) getCachedValue(TaskField.RESOURCE_INITIALS));
   }

   /**
    * The Resource Names field lists the names of all resources assigned
    * to a task.
    *
    * Note that MS Project 98 does not export values for this field when
    * writing an MPX file, and the field is not currently populated by MPXJ
    * when reading an MPP file.
    *
    * @return String containing a comma separated list of names
    */
   public String getResourceNames()
   {
      return ((String) getCachedValue(TaskField.RESOURCE_NAMES));
   }

   /**
    * The Resume field shows the date that the remaining portion of a task
    * is scheduled to resume after you enter a new value for the % Complete
    * field. The Resume field is also recalculated when the remaining portion
    * of a task is moved to a new date.
    *
    * @return Date
    */
   public Date getResume()
   {
      return ((Date) getCachedValue(TaskField.RESUME));
   }

   /**
    * For subtasks, the Rollup field indicates whether information on the
    * subtask Gantt bars
    * will be rolled up to the summary task bar. For summary tasks, the
    * Rollup field indicates
    * whether the summary task bar displays rolled up bars. You must
    * have the Rollup field for
    * summary tasks set to Yes for any subtasks to roll up to them.
    *
    * @return boolean
    */
   public boolean getRollup()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.ROLLUP)));
   }

   /**
    * The Start field shows the date and time that a task is scheduled to begin.
    * You can enter the start date you want, to indicate the date when the task
    * should begin. Or, you can have Microsoft Project calculate the start date.
    *
    * @return Date
    */
   public Date getStart()
   {
      return (Date) getCachedValue(TaskField.START);
   }

   /**
    * Retrieve the start text for a manually scheduled task.
    *
    * @return start text
    */
   public String getStartText()
   {
      return (String) getCachedValue(TaskField.START_TEXT);
   }

   /**
    * Set a start value.
    *
    * @param index start index (1-10)
    * @param value start value
    */
   public void setStart(int index, Date value)
   {
      set(selectField(TaskFieldLists.CUSTOM_START, index), value);
   }

   /**
    * Retrieve a start value.
    *
    * @param index start index (1-10)
    * @return start value
    */
   public Date getStart(int index)
   {
      return (Date) getCachedValue(selectField(TaskFieldLists.CUSTOM_START, index));
   }

   /**
    * Calculate the start variance.
    *
    * @return start variance
    */
   public Duration getStartVariance()
   {
      Duration variance = (Duration) getCachedValue(TaskField.START_VARIANCE);
      if (variance == null)
      {
         TimeUnit format = getParentFile().getProjectProperties().getDefaultDurationUnits();
         variance = DateHelper.getVariance(this, getBaselineStart(), getStart(), format);
         set(TaskField.START_VARIANCE, variance);
      }
      return (variance);
   }

   /**
    * The Stop field shows the date that represents the end of the actual
    * portion of a task. Typically, Microsoft Project calculates the stop date.
    * However, you can edit this date as well.
    *
    * @return Date
    */
   public Date getStop()
   {
      return ((Date) getCachedValue(TaskField.STOP));
   }

   /**
    * Contains the file name and path of the sub project represented by
    * the current task.
    *
    * @return sub project file path
    */
   public String getSubprojectName()
   {
      return ((String) getCachedValue(TaskField.SUBPROJECT_FILE));
   }

   /**
    * The Summary field indicates whether a task is a summary task.
    *
    * @return boolean, true-is summary task
    */
   public boolean getSummary()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.SUMMARY)));
   }

   /**
    * The SV (earned value schedule variance) field shows the difference in
    * cost terms between the current progress and the baseline plan of the
    * task up to the status date or today's date. You can use SV to
    * check costs to determine whether tasks are on schedule.
    *
    * @return -earned value schedule variance
    */
   public Number getSV()
   {
      Number variance = (Number) getCachedValue(TaskField.SV);
      if (variance == null)
      {
         Number bcwp = getBCWP();
         Number bcws = getBCWS();
         if (bcwp != null && bcws != null)
         {
            variance = NumberHelper.getDouble(bcwp.doubleValue() - bcws.doubleValue());
            set(TaskField.SV, variance);
         }
      }
      return (variance);
   }

   /**
    * Set a text value.
    *
    * @param index text index (1-30)
    * @param value text value
    */
   public void setText(int index, String value)
   {
      set(selectField(TaskFieldLists.CUSTOM_TEXT, index), value);
   }

   /**
    * Retrieve a text value.
    *
    * @param index text index (1-30)
    * @return text value
    */
   public String getText(int index)
   {
      return (String) getCachedValue(selectField(TaskFieldLists.CUSTOM_TEXT, index));
   }

   /**
    * Set an outline code value.
    *
    * @param index outline code index (1-10)
    * @param value outline code value
    */
   public void setOutlineCode(int index, String value)
   {
      set(selectField(TaskFieldLists.CUSTOM_OUTLINE_CODE, index), value);
   }

   /**
    * Retrieve an outline code value.
    *
    * @param index outline code index (1-10)
    * @return outline code value
    */
   public String getOutlineCode(int index)
   {
      return (String) getCachedValue(selectField(TaskFieldLists.CUSTOM_OUTLINE_CODE, index));
   }

   /**
    * The Total Slack field contains the amount of time a task can be
    * delayed without delaying the project's finish date.
    *
    * @return string representing duration
    */
   public Duration getTotalSlack()
   {
      Duration totalSlack = (Duration) getCachedValue(TaskField.TOTAL_SLACK);
      if (totalSlack == null)
      {
         Duration duration = getDuration();
         if (duration == null)
         {
            duration = Duration.getInstance(0, TimeUnit.DAYS);
         }

         TimeUnit units = duration.getUnits();

         Duration startSlack = getStartSlack();
         if (startSlack == null)
         {
            startSlack = Duration.getInstance(0, units);
         }
         else
         {
            if (startSlack.getUnits() != units)
            {
               startSlack = startSlack.convertUnits(units, getParentFile().getProjectProperties());
            }
         }

         Duration finishSlack = getFinishSlack();
         if (finishSlack == null)
         {
            finishSlack = Duration.getInstance(0, units);
         }
         else
         {
            if (finishSlack.getUnits() != units)
            {
               finishSlack = finishSlack.convertUnits(units, getParentFile().getProjectProperties());
            }
         }

         double startSlackDuration = startSlack.getDuration();
         double finishSlackDuration = finishSlack.getDuration();

         if (startSlackDuration == 0 || finishSlackDuration == 0)
         {
            if (startSlackDuration != 0)
            {
               totalSlack = startSlack;
            }
            else
            {
               totalSlack = finishSlack;
            }
         }
         else
         {
            if (startSlackDuration < finishSlackDuration)
            {
               totalSlack = startSlack;
            }
            else
            {
               totalSlack = finishSlack;
            }
         }

         set(TaskField.TOTAL_SLACK, totalSlack);
      }

      return (totalSlack);
   }

   /**
    * The Unique ID field contains the number that Microsoft Project
    * automatically designates whenever a new task is created. This number
    * indicates the sequence in which the task was
    * created, regardless of placement in the schedule.
    *
    * @return String
    */
   @Override public Integer getUniqueID()
   {
      return ((Integer) getCachedValue(TaskField.UNIQUE_ID));
   }

   /**
    * The Update Needed field indicates whether a TeamUpdate message
    * should be sent to the assigned resources because of changes to the
    * start date, finish date, or resource reassignments of the task.
    *
    * @return true if needed.
    */
   public boolean getUpdateNeeded()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.UPDATE_NEEDED)));
   }

   /**
    * The work breakdown structure code. The WBS field contains an
    * alphanumeric code you can use to represent the task's position within
    * the hierarchical structure of the project. This field is similar to
    * the outline number, except that you can edit it.
    *
    * @return string
    */
   public String getWBS()
   {
      return ((String) getCachedValue(TaskField.WBS));
   }

   /**
    * The Work field shows the total amount of work scheduled to be performed
    * on a task by all assigned resources. This field shows the total work,
    * or person-hours, for a task.
    *
    * @return Duration representing duration .
    */
   public Duration getWork()
   {
      return ((Duration) getCachedValue(TaskField.WORK));
   }

   /**
    * The Work Variance field contains the difference between a task's
    * baseline work and the currently scheduled work.
    *
    * @return Duration representing duration.
    */
   public Duration getWorkVariance()
   {
      Duration variance = (Duration) getCachedValue(TaskField.WORK_VARIANCE);
      if (variance == null)
      {
         Duration work = getWork();
         Duration baselineWork = getBaselineWork();
         if (work != null && baselineWork != null)
         {
            variance = Duration.getInstance(work.getDuration() - baselineWork.convertUnits(work.getUnits(), getParentFile().getProjectProperties()).getDuration(), work.getUnits());
            set(TaskField.WORK_VARIANCE, variance);
         }
      }
      return (variance);
   }

   /**
    * This method retrieves a reference to the parent of this task, as
    * defined by the outline level. If this task is at the top level,
    * this method will return null.
    *
    * @return parent task
    */
   public Task getParentTask()
   {
      return (m_parent);
   }

   /**
    * This method retrieves a list of child tasks relative to the
    * current task, as defined by the outine level. If there
    * are no child tasks, this method will return an empty list.
    *
    * @return child tasks
    */
   @Override public List<Task> getChildTasks()
   {
      return (m_children);
   }

   /**
    * This method implements the only method in the Comparable interface.
    * This allows Tasks to be compared and sorted based on their ID value.
    * Note that if the MPX/MPP file has been generated by MSP, the ID value
    * will always be in the correct sequence. The Unique ID value will not
    * necessarily be in the correct sequence as task insertions and deletions
    * will change the order.
    *
    * @param o object to compare this instance with
    * @return result of comparison
    */
   @Override public int compareTo(Task o)
   {
      int id1 = NumberHelper.getInt(getID());
      int id2 = NumberHelper.getInt(o.getID());
      return ((id1 < id2) ? (-1) : ((id1 == id2) ? 0 : 1));
   }

   /**
    * This method retrieves a flag indicating whether the duration of the
    * task has only been estimated.
    *
    * @return boolean
    */
   public boolean getEstimated()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.ESTIMATED)));
   }

   /**
    * This method retrieves a flag indicating whether the duration of the
    * task has only been estimated.
   
    * @param estimated Boolean flag
    */
   public void setEstimated(boolean estimated)
   {
      set(TaskField.ESTIMATED, estimated);
   }

   /**
    * This method retrieves the deadline for this task.
    *
    * @return Task deadline
    */
   public Date getDeadline()
   {
      return ((Date) getCachedValue(TaskField.DEADLINE));
   }

   /**
    * This method sets the deadline for this task.
    *
    * @param deadline deadline date
    */
   public void setDeadline(Date deadline)
   {
      set(TaskField.DEADLINE, deadline);
   }

   /**
    * This method retrieves the task type.
    *
    * @return int representing the task type
    */
   public TaskType getType()
   {
      return ((TaskType) getCachedValue(TaskField.TYPE));
   }

   /**
    * This method sets the task type.
    *
    * @param type task type
    */
   public void setType(TaskType type)
   {
      set(TaskField.TYPE, type);
   }

   /**
    * Retrieves the flag indicating if this is a null task.
    *
    * @return boolean flag
    */
   public boolean getNull()
   {
      return (m_null);
   }

   /**
    * Sets the flag indicating if this is a null task.
    *
    * @param isNull boolean flag
    */
   public void setNull(boolean isNull)
   {
      m_null = isNull;
   }

   /**
    * Retrieve the resume valid flag.
    *
    * @return resume valid flag
    */
   public boolean getResumeValid()
   {
      return (m_resumeValid);
   }

   /**
    * Set the resume valid flag.
    *
    * @param resumeValid resume valid flag
    */
   public void setResumeValid(boolean resumeValid)
   {
      m_resumeValid = resumeValid;
   }

   /**
    * Retrieve the recurring flag.
    *
    * @return recurring flag
    */
   public boolean getRecurring()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.RECURRING)));
   }

   /**
    * Set the recurring flag.
    *
    * @param recurring recurring flag
    */
   public void setRecurring(boolean recurring)
   {
      set(TaskField.RECURRING, recurring);
   }

   /**
    * Retrieve the over allocated flag.
    *
    * @return over allocated flag
    */
   public boolean getOverAllocated()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.OVERALLOCATED)));
   }

   /**
    * Set the over allocated flag.
    *
    * @param overAllocated over allocated flag
    */
   public void setOverAllocated(boolean overAllocated)
   {
      set(TaskField.OVERALLOCATED, overAllocated);
   }

   /**
    * Where a task in an MPP file represents a task from a subproject,
    * this value will be non-zero. The value itself is the unique ID
    * value shown in the parent project. To retrieve the value of the
    * task unique ID in the child project, remove the top two bytes:
    *
    * taskID = (subprojectUniqueID & 0xFFFF)
    *
    * @return sub project unique task ID
    */
   public Integer getSubprojectTaskUniqueID()
   {
      return (Integer) getCachedValue(TaskField.SUBPROJECT_UNIQUE_TASK_ID);
   }

   /**
    * Sets the sub project unique task ID.
    *
    * @param subprojectUniqueTaskID subproject unique task ID
    */
   public void setSubprojectTaskUniqueID(Integer subprojectUniqueTaskID)
   {
      set(TaskField.SUBPROJECT_UNIQUE_TASK_ID, subprojectUniqueTaskID);
   }

   /**
    * Where a task in an MPP file represents a task from a subproject,
    * this value will be non-zero. The value itself is the ID
    * value shown in the parent project.
    *
    * @return sub project task ID
    */
   public Integer getSubprojectTaskID()
   {
      return (Integer) getCachedValue(TaskField.SUBPROJECT_TASK_ID);
   }

   /**
    * Sets the sub project task ID.
    *
    * @param subprojectTaskID subproject task ID
    */
   public void setSubprojectTaskID(Integer subprojectTaskID)
   {
      set(TaskField.SUBPROJECT_TASK_ID, subprojectTaskID);
   }

   /**
    * Sets the offset added to unique task IDs from sub projects
    * to generate the task ID shown in the master project.
    *
    * @param offset unique ID offset
    */
   public void setSubprojectTasksUniqueIDOffset(Integer offset)
   {
      set(TaskField.SUBPROJECT_TASKS_UNIQUEID_OFFSET, offset);
   }

   /**
    * Retrieves the offset added to unique task IDs from sub projects
    * to generate the task ID shown in the master project.
    *
    * @return unique ID offset
    */
   public Integer getSubprojectTasksUniqueIDOffset()
   {
      return (Integer) getCachedValue(TaskField.SUBPROJECT_TASKS_UNIQUEID_OFFSET);
   }

   /**
    * Retrieve the subproject read only flag.
    *
    * @return subproject read only flag
    */
   public boolean getSubprojectReadOnly()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.SUBPROJECT_READ_ONLY)));
   }

   /**
    * Set the subproject read only flag.
    *
    * @param subprojectReadOnly subproject read only flag
    */
   public void setSubprojectReadOnly(boolean subprojectReadOnly)
   {
      set(TaskField.SUBPROJECT_READ_ONLY, subprojectReadOnly);
   }

   /**
    * Retrieves the external task flag.
    *
    * @return external task flag
    */
   public boolean getExternalTask()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.EXTERNAL_TASK)));
   }

   /**
    * Sets the external task flag.
    *
    * @param externalTask external task flag
    */
   public void setExternalTask(boolean externalTask)
   {
      set(TaskField.EXTERNAL_TASK, externalTask);
   }

   /**
    * Retrieves the external task project file name.
    *
    * @return external task project file name
    */
   public String getExternalTaskProject()
   {
      return (m_externalTaskProject);
   }

   /**
    * Sets the external task project file name.
    *
    * @param externalTaskProject external task project file name
    */
   public void setExternalTaskProject(String externalTaskProject)
   {
      m_externalTaskProject = externalTaskProject;
   }

   /**
    * Retrieve the ACWP value.
    *
    * @return ACWP value
    */
   public Number getACWP()
   {
      return ((Number) getCachedValue(TaskField.ACWP));
   }

   /**
    * Set the ACWP value.
    *
    * @param acwp ACWP value
    */
   public void setACWP(Number acwp)
   {
      set(TaskField.ACWP, acwp);
   }

   /**
    * Retrieve the leveling delay format.
    *
    * @return leveling delay  format
    */
   public TimeUnit getLevelingDelayFormat()
   {
      return (TimeUnit) getCachedValue(TaskField.LEVELING_DELAY_UNITS);
   }

   /**
    * Set the leveling delay format.
    *
    * @param levelingDelayFormat leveling delay format
    */
   public void setLevelingDelayFormat(TimeUnit levelingDelayFormat)
   {
      set(TaskField.LEVELING_DELAY_UNITS, levelingDelayFormat);
   }

   /**
    * Retrieves the ignore resource calendar flag.
    *
    * @return ignore resource calendar flag
    */
   public boolean getIgnoreResourceCalendar()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.IGNORE_RESOURCE_CALENDAR)));
   }

   /**
    * Sets the ignore resource calendar flag.
    *
    * @param ignoreResourceCalendar ignore resource calendar flag
    */
   public void setIgnoreResourceCalendar(boolean ignoreResourceCalendar)
   {
      set(TaskField.IGNORE_RESOURCE_CALENDAR, ignoreResourceCalendar);
   }

   /**
    * Retrieves the physical percent complete value.
    *
    * @return physical percent complete value
    */
   public Number getPhysicalPercentComplete()
   {
      return (Number) getCachedValue(TaskField.PHYSICAL_PERCENT_COMPLETE);
   }

   /**
    * Sets the physical percent complete value.
    *
    * @param physicalPercentComplete physical percent complete value
    */
   public void setPhysicalPercentComplete(Number physicalPercentComplete)
   {
      set(TaskField.PHYSICAL_PERCENT_COMPLETE, physicalPercentComplete);
   }

   /**
    * Retrieves the earned value method.
    *
    * @return earned value method
    */
   public EarnedValueMethod getEarnedValueMethod()
   {
      return (EarnedValueMethod) getCachedValue(TaskField.EARNED_VALUE_METHOD);
   }

   /**
    * Sets the earned value method.
    *
    * @param earnedValueMethod earned value method
    */
   public void setEarnedValueMethod(EarnedValueMethod earnedValueMethod)
   {
      set(TaskField.EARNED_VALUE_METHOD, earnedValueMethod);
   }

   /**
    * Retrieves the actual work protected value.
    *
    * @return actual work protected value
    */
   public Duration getActualWorkProtected()
   {
      return (Duration) getCachedValue(TaskField.ACTUAL_WORK_PROTECTED);
   }

   /**
    * Sets the actual work protected value.
    *
    * @param actualWorkProtected actual work protected value
    */
   public void setActualWorkProtected(Duration actualWorkProtected)
   {
      set(TaskField.ACTUAL_WORK_PROTECTED, actualWorkProtected);
   }

   /**
    * Retrieves the actual overtime work protected value.
    *
    * @return actual overtime work protected value
    */
   public Duration getActualOvertimeWorkProtected()
   {
      return (Duration) getCachedValue(TaskField.ACTUAL_OVERTIME_WORK_PROTECTED);
   }

   /**
    * Sets the actual overtime work protected value.
    *
    * @param actualOvertimeWorkProtected actual overtime work protected value
    */
   public void setActualOvertimeWorkProtected(Duration actualOvertimeWorkProtected)
   {
      set(TaskField.ACTUAL_OVERTIME_WORK_PROTECTED, actualOvertimeWorkProtected);
   }

   /**
    * Retrieve the amount of regular work.
    *
    * @return amount of regular work
    */
   public Duration getRegularWork()
   {
      return ((Duration) getCachedValue(TaskField.REGULAR_WORK));
   }

   /**
    * Set the amount of regular work.
    *
    * @param regularWork amount of regular work
    */
   public void setRegularWork(Duration regularWork)
   {
      set(TaskField.REGULAR_WORK, regularWork);
   }

   /**
    * Sets the effort driven flag.
    *
    * @param flag value
    */
   public void setEffortDriven(boolean flag)
   {
      set(TaskField.EFFORT_DRIVEN, flag);
   }

   /**
    * Retrieves the effort driven flag.
    *
    * @return Flag value
    */
   public boolean getEffortDriven()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.EFFORT_DRIVEN)));
   }

   /**
    * Set a date value.
    *
    * @param index date index (1-10)
    * @param value date value
    */
   public void setDate(int index, Date value)
   {
      set(selectField(TaskFieldLists.CUSTOM_DATE, index), value);
   }

   /**
    * Retrieve a date value.
    *
    * @param index date index (1-10)
    * @return date value
    */
   public Date getDate(int index)
   {
      return (Date) getCachedValue(selectField(TaskFieldLists.CUSTOM_DATE, index));
   }

   /**
    * Retrieves the overtime cost.
    *
    * @return Cost value
    */
   public Number getOvertimeCost()
   {
      return ((Number) getCachedValue(TaskField.OVERTIME_COST));
   }

   /**
    * Sets the overtime cost value.
    *
    * @param number Cost value
    */
   public void setOvertimeCost(Number number)
   {
      set(TaskField.OVERTIME_COST, number);
   }

   /**
    * Retrieves the actual overtime cost for this task.
    *
    * @return actual overtime cost
    */
   public Number getActualOvertimeCost()
   {
      return ((Number) getCachedValue(TaskField.ACTUAL_OVERTIME_COST));
   }

   /**
    * Sets the actual overtime cost for this task.
    *
    * @param cost actual overtime cost
    */
   public void setActualOvertimeCost(Number cost)
   {
      set(TaskField.ACTUAL_OVERTIME_COST, cost);
   }

   /**
    * Retrieves the actual overtime work value.
    *
    * @return actual overtime work value
    */
   public Duration getActualOvertimeWork()
   {
      return ((Duration) getCachedValue(TaskField.ACTUAL_OVERTIME_WORK));
   }

   /**
    * Sets the actual overtime work value.
    *
    * @param work actual overtime work value
    */
   public void setActualOvertimeWork(Duration work)
   {
      set(TaskField.ACTUAL_OVERTIME_WORK, work);
   }

   /**
    * Retrieves the fixed cost accrual flag value.
    *
    * @return fixed cost accrual flag
    */
   public AccrueType getFixedCostAccrual()
   {
      return ((AccrueType) getCachedValue(TaskField.FIXED_COST_ACCRUAL));
   }

   /**
    * Sets the fixed cost accrual flag value.
    *
    * @param type fixed cost accrual type
    */
   public void setFixedCostAccrual(AccrueType type)
   {
      set(TaskField.FIXED_COST_ACCRUAL, type);
   }

   /**
    * Retrieves the task hyperlink attribute.
    *
    * @return hyperlink attribute
    */
   public String getHyperlink()
   {
      return ((String) getCachedValue(TaskField.HYPERLINK));
   }

   /**
    * Retrieves the task hyperlink address attribute.
    *
    * @return hyperlink address attribute
    */
   public String getHyperlinkAddress()
   {
      return ((String) getCachedValue(TaskField.HYPERLINK_ADDRESS));
   }

   /**
    * Retrieves the task hyperlink sub-address attribute.
    *
    * @return hyperlink sub address attribute
    */
   public String getHyperlinkSubAddress()
   {
      return ((String) getCachedValue(TaskField.HYPERLINK_SUBADDRESS));
   }

   /**
    * Sets the task hyperlink attribute.
    *
    * @param text hyperlink attribute
    */
   public void setHyperlink(String text)
   {
      set(TaskField.HYPERLINK, text);
   }

   /**
    * Sets the task hyperlink address attribute.
    *
    * @param text hyperlink address attribute
    */
   public void setHyperlinkAddress(String text)
   {
      set(TaskField.HYPERLINK_ADDRESS, text);
   }

   /**
    * Sets the task hyperlink sub address attribute.
    *
    * @param text hyperlink sub address attribute
    */
   public void setHyperlinkSubAddress(String text)
   {
      set(TaskField.HYPERLINK_SUBADDRESS, text);
   }

   /**
    * Retrieves the level assignments flag.
    *
    * @return level assignments flag
    */
   public boolean getLevelAssignments()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.LEVEL_ASSIGNMENTS)));
   }

   /**
    * Sets the level assignments flag.
    *
    * @param flag level assignments flag
    */
   public void setLevelAssignments(boolean flag)
   {
      set(TaskField.LEVEL_ASSIGNMENTS, flag);
   }

   /**
    * Retrieves the leveling can split flag.
    *
    * @return leveling can split flag
    */
   public boolean getLevelingCanSplit()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.LEVELING_CAN_SPLIT)));
   }

   /**
    * Sets the leveling can split flag.
    *
    * @param flag leveling can split flag
    */
   public void setLevelingCanSplit(boolean flag)
   {
      set(TaskField.LEVELING_CAN_SPLIT, flag);
   }

   /**
    * Retrieves the overtime work attribute.
    *
    * @return overtime work value
    */
   public Duration getOvertimeWork()
   {
      return ((Duration) getCachedValue(TaskField.OVERTIME_WORK));
   }

   /**
    * Sets the overtime work attribute.
    *
    * @param work overtime work value
    */
   public void setOvertimeWork(Duration work)
   {
      set(TaskField.OVERTIME_WORK, work);
   }

   /**
    * Retrieves the preleveled start attribute.
    *
    * @return preleveled start
    */
   public Date getPreleveledStart()
   {
      return ((Date) getCachedValue(TaskField.PRELEVELED_START));
   }

   /**
    * Retrieves the preleveled finish attribute.
    *
    * @return preleveled finish
    */
   public Date getPreleveledFinish()
   {
      return ((Date) getCachedValue(TaskField.PRELEVELED_FINISH));
   }

   /**
    * Sets the preleveled start attribute.
    *
    * @param date preleveled start attribute
    */
   public void setPreleveledStart(Date date)
   {
      set(TaskField.PRELEVELED_START, date);
   }

   /**
    * Sets the preleveled finish attribute.
    *
    * @param date preleveled finish attribute
    */
   public void setPreleveledFinish(Date date)
   {
      set(TaskField.PRELEVELED_FINISH, date);
   }

   /**
    * Retrieves the remaining overtime work attribute.
    *
    * @return remaining overtime work
    */
   public Duration getRemainingOvertimeWork()
   {
      return ((Duration) getCachedValue(TaskField.REMAINING_OVERTIME_WORK));
   }

   /**
    * Sets the remaining overtime work attribute.
    *
    * @param work remaining overtime work
    */
   public void setRemainingOvertimeWork(Duration work)
   {
      set(TaskField.REMAINING_OVERTIME_WORK, work);
   }

   /**
    * Retrieves the remaining overtime cost.
    *
    * @return remaining overtime cost value
    */
   public Number getRemainingOvertimeCost()
   {
      return ((Number) getCachedValue(TaskField.REMAINING_OVERTIME_COST));
   }

   /**
    * Sets the remaining overtime cost value.
    *
    * @param cost overtime cost value
    */
   public void setRemainingOvertimeCost(Number cost)
   {
      set(TaskField.REMAINING_OVERTIME_COST, cost);
   }

   /**
    * Retrieves the base calendar instance associated with this task.
    * Note that this attribute appears in MPP9 and MSPDI files.
    *
    * @return ProjectCalendar instance
    */
   public ProjectCalendar getCalendar()
   {
      return ((ProjectCalendar) getCachedValue(TaskField.CALENDAR));
   }

   /**
    * Set the calendar unique ID.
    *
    * @param id calendar unique ID
    */
   public void setCalendarUniqueID(Integer id)
   {
      set(TaskField.CALENDAR_UNIQUE_ID, id);
   }

   /**
    * Retrieve the calendar unique ID.
    *
    * @return calendar unique ID
    */
   public Integer getCalendarUniqueID()
   {
      return (Integer) getCachedValue(TaskField.CALENDAR_UNIQUE_ID);
   }

   /**
    * Sets the name of the base calendar associated with this task.
    * Note that this attribute appears in MPP9 and MSPDI files.
    *
    * @param calendar calendar instance
    */
   public void setCalendar(ProjectCalendar calendar)
   {
      set(TaskField.CALENDAR, calendar);
      setCalendarUniqueID(calendar == null ? null : calendar.getUniqueID());
   }

   /**
    * Retrieve a flag indicating if the task is shown as expanded
    * in MS Project. If this flag is set to true, any sub tasks
    * for this current task will be visible. If this is false,
    * any sub tasks will be hidden.
    *
    * @return boolean flag
    */
   public boolean getExpanded()
   {
      return (m_expanded);
   }

   /**
    * Set a flag indicating if the task is shown as expanded
    * in MS Project. If this flag is set to true, any sub tasks
    * for this current task will be visible. If this is false,
    * any sub tasks will be hidden.
    *
    * @param expanded boolean flag
    */
   public void setExpanded(boolean expanded)
   {
      m_expanded = expanded;
   }

   /**
    * Set the start slack.
    *
    * @param duration start slack
    */
   public void setStartSlack(Duration duration)
   {
      set(TaskField.START_SLACK, duration);
   }

   /**
    * Set the finish slack.
    *
    * @param duration finish slack
    */
   public void setFinishSlack(Duration duration)
   {
      set(TaskField.FINISH_SLACK, duration);
   }

   /**
    * Retrieve the start slack.
    *
    * @return start slack
    */
   public Duration getStartSlack()
   {
      Duration startSlack = (Duration) getCachedValue(TaskField.START_SLACK);
      if (startSlack == null)
      {
         Duration duration = getDuration();
         if (duration != null)
         {
            startSlack = DateHelper.getVariance(this, getEarlyStart(), getLateStart(), duration.getUnits());
            set(TaskField.START_SLACK, startSlack);
         }
      }
      return (startSlack);
   }

   /**
    * Retrieve the finish slack.
    *
    * @return finish slack
    */
   public Duration getFinishSlack()
   {
      Duration finishSlack = (Duration) getCachedValue(TaskField.FINISH_SLACK);
      if (finishSlack == null)
      {
         Duration duration = getDuration();
         if (duration != null)
         {
            finishSlack = DateHelper.getVariance(this, getEarlyFinish(), getLateFinish(), duration.getUnits());
            set(TaskField.FINISH_SLACK, finishSlack);
         }
      }
      return (finishSlack);
   }

   /**
    * Retrieve the value of a field using its alias.
    *
    * @param alias field alias
    * @return field value
    */
   public Object getFieldByAlias(String alias)
   {
      return getCachedValue(getParentFile().getCustomFields().getFieldByAlias(FieldTypeClass.TASK, alias));
   }

   /**
    * Set the value of a field using its alias.
    *
    * @param alias field alias
    * @param value field value
    */
   public void setFieldByAlias(String alias, Object value)
   {
      set(getParentFile().getCustomFields().getFieldByAlias(FieldTypeClass.TASK, alias), value);
   }

   /**
    * This method retrieves a list of task splits. Each split is represented
    * by a DateRange instance. The list will always follow the pattern
    * task range, split range, task range and so on.
    *
    * Note that this method will return null if the task is not split.
    *
    * @return list of split times
    */
   @SuppressWarnings("unchecked") public List<DateRange> getSplits()
   {
      return (List<DateRange>) getCachedValue(TaskField.SPLITS);
   }

   /**
    * Internal method used to set the list of splits.
    *
    * @param splits list of split times
    */
   public void setSplits(List<DateRange> splits)
   {
      set(TaskField.SPLITS, splits);
   }

   /**
    * Task splits contain the time up to which the splits are completed.
    *
    * @return Duration of completed time for the splits.
    */
   public Date getSplitCompleteDuration()
   {
      return (Date) getCachedValue(TaskField.SPLITS_COMPLETE);
   }

   /**
    * Set the time up to which the splits are completed.
    *
    * @param splitsComplete Duration of completed time for the splits.
    */
   public void setSplitCompleteDuration(Date splitsComplete)
   {
      set(TaskField.SPLITS_COMPLETE, splitsComplete);
   }

   /**
    * Removes this task from the project.
    */
   public void remove()
   {
      getParentFile().removeTask(this);
   }

   /**
    * Retrieve the sub project represented by this task.
    *
    * @return sub project
    */
   public SubProject getSubProject()
   {
      return (SubProject) getCachedValue(TaskField.SUBPROJECT);
   }

   /**
    * Set the sub project represented by this task.
    *
    * @param subProject sub project
    */
   public void setSubProject(SubProject subProject)
   {
      set(TaskField.SUBPROJECT, subProject);
   }

   /**
    * Retrieve an enterprise field value.
    *
    * @param index field index
    * @return field value
    */
   public Number getEnterpriseCost(int index)
   {
      return ((Number) getCachedValue(selectField(TaskFieldLists.ENTERPRISE_COST, index)));
   }

   /**
    * Set an enterprise field value.
    *
    * @param index field index
    * @param value field value
    */
   public void setEnterpriseCost(int index, Number value)
   {
      set(selectField(TaskFieldLists.ENTERPRISE_COST, index), value);
   }

   /**
    * Retrieve an enterprise field value.
    *
    * @param index field index
    * @return field value
    */
   public Date getEnterpriseDate(int index)
   {
      return ((Date) getCachedValue(selectField(TaskFieldLists.ENTERPRISE_DATE, index)));
   }

   /**
    * Set an enterprise field value.
    *
    * @param index field index
    * @param value field value
    */
   public void setEnterpriseDate(int index, Date value)
   {
      set(selectField(TaskFieldLists.ENTERPRISE_DATE, index), value);
   }

   /**
    * Retrieve an enterprise field value.
    *
    * @param index field index
    * @return field value
    */
   public Duration getEnterpriseDuration(int index)
   {
      return ((Duration) getCachedValue(selectField(TaskFieldLists.ENTERPRISE_DURATION, index)));
   }

   /**
    * Set an enterprise field value.
    *
    * @param index field index
    * @param value field value
    */
   public void setEnterpriseDuration(int index, Duration value)
   {
      set(selectField(TaskFieldLists.ENTERPRISE_DURATION, index), value);
   }

   /**
    * Retrieve an enterprise field value.
    *
    * @param index field index
    * @return field value
    */
   public boolean getEnterpriseFlag(int index)
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(selectField(TaskFieldLists.ENTERPRISE_FLAG, index))));
   }

   /**
    * Set an enterprise field value.
    *
    * @param index field index
    * @param value field value
    */
   public void setEnterpriseFlag(int index, boolean value)
   {
      set(selectField(TaskFieldLists.ENTERPRISE_FLAG, index), value);
   }

   /**
    * Retrieve an enterprise field value.
    *
    * @param index field index
    * @return field value
    */
   public Number getEnterpriseNumber(int index)
   {
      return ((Number) getCachedValue(selectField(TaskFieldLists.ENTERPRISE_NUMBER, index)));
   }

   /**
    * Set an enterprise field value.
    *
    * @param index field index
    * @param value field value
    */
   public void setEnterpriseNumber(int index, Number value)
   {
      set(selectField(TaskFieldLists.ENTERPRISE_NUMBER, index), value);
   }

   /**
    * Retrieve an enterprise field value.
    *
    * @param index field index
    * @return field value
    */
   public String getEnterpriseText(int index)
   {
      return ((String) getCachedValue(selectField(TaskFieldLists.ENTERPRISE_TEXT, index)));
   }

   /**
    * Set an enterprise field value.
    *
    * @param index field index
    * @param value field value
    */
   public void setEnterpriseText(int index, String value)
   {
      set(selectField(TaskFieldLists.ENTERPRISE_TEXT, index), value);
   }

   /**
    * Retrieve an enterprise custom field value.
    *
    * @param index field index
    * @return field value
    */
   public String getEnterpriseCustomField(int index)
   {
      return ((String) getCachedValue(selectField(TaskFieldLists.ENTERPRISE_CUSTOM_FIELD, index)));
   }

   /**
    * Set an enterprise custom field value.
    *
    * @param index field index
    * @param value field value
    */
   public void setEnterpriseCustomField(int index, String value)
   {
      set(selectField(TaskFieldLists.ENTERPRISE_CUSTOM_FIELD, index), value);
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineCost(int baselineNumber, Number value)
   {
      set(selectField(TaskFieldLists.BASELINE_COSTS, baselineNumber), value);
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineDuration(int baselineNumber, Duration value)
   {
      set(selectField(TaskFieldLists.BASELINE_DURATIONS, baselineNumber), value);
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineFinish(int baselineNumber, Date value)
   {
      set(selectField(TaskFieldLists.BASELINE_FINISHES, baselineNumber), value);
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineStart(int baselineNumber, Date value)
   {
      set(selectField(TaskFieldLists.BASELINE_STARTS, baselineNumber), value);
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineWork(int baselineNumber, Duration value)
   {
      set(selectField(TaskFieldLists.BASELINE_WORKS, baselineNumber), value);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Number getBaselineCost(int baselineNumber)
   {
      return ((Number) getCachedValue(selectField(TaskFieldLists.BASELINE_COSTS, baselineNumber)));
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Duration getBaselineDuration(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_DURATIONS, baselineNumber));
      if (result == null)
      {
         result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_DURATIONS, baselineNumber));
      }

      if (!(result instanceof Duration))
      {
         result = null;
      }
      return (Duration) result;
   }

   /**
    * Retrieves the baseline duration text value.
    *
    * @param baselineNumber baseline number
    * @return baseline duration text value
    */
   public String getBaselineDurationText(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_DURATIONS, baselineNumber));
      if (result == null)
      {
         result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_DURATIONS, baselineNumber));
      }

      if (!(result instanceof String))
      {
         result = null;
      }
      return (String) result;
   }

   /**
    * Sets the baseline duration text value.
    *
    * @param baselineNumber baseline number
    * @param value baseline duration text value
    */
   public void setBaselineDurationText(int baselineNumber, String value)
   {
      set(selectField(TaskFieldLists.BASELINE_DURATIONS, baselineNumber), value);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Date getBaselineFinish(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_FINISHES, baselineNumber));
      if (result == null)
      {
         result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_FINISHES, baselineNumber));
      }

      if (!(result instanceof Date))
      {
         result = null;
      }
      return (Date) result;
   }

   /**
    * Retrieves the baseline finish text value.
    *
    * @param baselineNumber baseline number
    * @return baseline finish text value
    */
   public String getBaselineFinishText(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_FINISHES, baselineNumber));
      if (result == null)
      {
         result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_FINISHES, baselineNumber));
      }

      if (!(result instanceof String))
      {
         result = null;
      }
      return (String) result;
   }

   /**
    * Sets the baseline finish text value.
    *
    * @param baselineNumber baseline number
    * @param value baseline finish text value
    */
   public void setBaselineFinishText(int baselineNumber, String value)
   {
      set(selectField(TaskFieldLists.BASELINE_FINISHES, baselineNumber), value);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Date getBaselineStart(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_STARTS, baselineNumber));
      if (result == null)
      {
         result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_STARTS, baselineNumber));
      }

      if (!(result instanceof Date))
      {
         result = null;
      }
      return (Date) result;
   }

   /**
    * Retrieves the baseline start text value.
    *
    * @param baselineNumber baseline number
    * @return baseline start text value
    */
   public String getBaselineStartText(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_STARTS, baselineNumber));
      if (result == null)
      {
         result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_STARTS, baselineNumber));
      }

      if (!(result instanceof String))
      {
         result = null;
      }
      return (String) result;
   }

   /**
    * Sets the baseline start text value.
    *
    * @param baselineNumber baseline number
    * @param value baseline start text value
    */
   public void setBaselineStartText(int baselineNumber, String value)
   {
      set(selectField(TaskFieldLists.BASELINE_STARTS, baselineNumber), value);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Duration getBaselineWork(int baselineNumber)
   {
      return ((Duration) getCachedValue(selectField(TaskFieldLists.BASELINE_WORKS, baselineNumber)));
   }

   /**
    * Retrieve the "complete through" date.
    *
    * @return complete through date
    */
   public Date getCompleteThrough()
   {
      Date value = (Date) getCachedValue(TaskField.COMPLETE_THROUGH);
      if (value == null)
      {
         int percentComplete = NumberHelper.getInt(getPercentageComplete());
         switch (percentComplete)
         {
            case 0:
            {
               break;
            }

            case 100:
            {
               value = getActualFinish();
               break;
            }

            default:
            {
               Date actualStart = getActualStart();
               Duration duration = getDuration();
               if (actualStart != null && duration != null)
               {
                  double durationValue = (duration.getDuration() * percentComplete) / 100d;
                  duration = Duration.getInstance(durationValue, duration.getUnits());
                  ProjectCalendar calendar = getEffectiveCalendar();
                  value = calendar.getDate(actualStart, duration, true);
               }
               break;
            }
         }

         set(TaskField.COMPLETE_THROUGH, value);
      }
      return value;
   }

   /**
    * Retrieve the summary progress date.
    *
    * @return summary progress date
    */
   public Date getSummaryProgress()
   {
      Date value = (Date) getCachedValue(TaskField.SUMMARY_PROGRESS);
      return value;
   }

   /**
    * Set the summary progress date.
    *
    * @param value summary progress date
    */
   public void setSummaryProgress(Date value)
   {
      set(TaskField.SUMMARY_PROGRESS, value);
   }

   /**
    * Retrieve the task GUID.
    *
    * @return task GUID
    */
   public UUID getGUID()
   {
      return (UUID) getCachedValue(TaskField.GUID);
   }

   /**
    * Set the task GUID.
    *
    * @param value task GUID
    */
   public void setGUID(UUID value)
   {
      set(TaskField.GUID, value);
   }

   /**
    * Retrieves the task mode.
    *
    * @return task mode
    */
   public TaskMode getTaskMode()
   {
      return BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.TASK_MODE)) ? TaskMode.MANUALLY_SCHEDULED : TaskMode.AUTO_SCHEDULED;
   }

   /**
    * Sets the task mode.
    *
    * @param mode task mode
    */
   public void setTaskMode(TaskMode mode)
   {
      set(TaskField.TASK_MODE, mode == TaskMode.MANUALLY_SCHEDULED);
   }

   /**
    * Retrieves the active flag.
    *
    * @return active flag value
    */
   public boolean getActive()
   {
      return (BooleanHelper.getBoolean((Boolean) getCachedValue(TaskField.ACTIVE)));
   }

   /**
    * Sets the active flag.
    *
    * @param active active flag value
    */
   public void setActive(boolean active)
   {
      set(TaskField.ACTIVE, active);
   }

   /**
    * Retrieve the baseline estimated duration.
    *
    * @return baseline estimated duration
    */
   public Duration getBaselineEstimatedDuration()
   {
      return (Duration) getCachedValue(TaskField.BASELINE_ESTIMATED_DURATION);
   }

   /**
    * Set the baseline estimated duration.
    *
    * @param duration baseline estimated duration
    */
   public void setBaselineEstimatedDuration(Duration duration)
   {
      set(TaskField.BASELINE_ESTIMATED_DURATION, duration);
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineEstimatedDuration(int baselineNumber, Duration value)
   {
      set(selectField(TaskFieldLists.BASELINE_ESTIMATED_DURATIONS, baselineNumber), value);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Duration getBaselineEstimatedDuration(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_DURATIONS, baselineNumber));
      if (!(result instanceof Duration))
      {
         result = null;
      }
      return (Duration) result;
   }

   /**
    * Retrieve the baseline estimated start.
    *
    * @return baseline estimated start
    */
   public Date getBaselineEstimatedStart()
   {
      return (Date) getCachedValue(TaskField.BASELINE_ESTIMATED_START);
   }

   /**
    * Set the baseline estimated start.
    *
    * @param date baseline estimated start
    */
   public void setBaselineEstimatedStart(Date date)
   {
      set(TaskField.BASELINE_ESTIMATED_START, date);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Date getBaselineEstimatedStart(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_STARTS, baselineNumber));
      if (!(result instanceof Date))
      {
         result = null;
      }
      return (Date) result;
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineEstimatedStart(int baselineNumber, Date value)
   {
      set(selectField(TaskFieldLists.BASELINE_ESTIMATED_STARTS, baselineNumber), value);
   }

   /**
    * Retrieve the baseline estimated finish.
    *
    * @return baseline estimated finish
    */
   public Date getBaselineEstimatedFinish()
   {
      return (Date) getCachedValue(TaskField.BASELINE_ESTIMATED_FINISH);
   }

   /**
    * Set the baseline estimated finish.
    *
    * @param date baseline estimated finish
    */
   public void setBaselineEstimatedFinish(Date date)
   {
      set(TaskField.BASELINE_ESTIMATED_FINISH, date);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Date getBaselineEstimatedFinish(int baselineNumber)
   {
      Object result = getCachedValue(selectField(TaskFieldLists.BASELINE_ESTIMATED_FINISHES, baselineNumber));
      if (!(result instanceof Date))
      {
         result = null;
      }
      return (Date) result;
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineEstimatedFinish(int baselineNumber, Date value)
   {
      set(selectField(TaskFieldLists.BASELINE_ESTIMATED_FINISHES, baselineNumber), value);
   }

   /**
    * The Fixed Cost field shows any task expense that is not associated
    * with a resource cost.
    *
    * @param val amount
    */
   public void setBaselineFixedCost(Number val)
   {
      set(TaskField.BASELINE_FIXED_COST, val);
   }

   /**
    * The Fixed Cost field shows any task expense that is not associated
    * with a resource cost.
    *
    * @return currency amount
    */
   public Number getBaselineFixedCost()
   {
      return ((Number) getCachedValue(TaskField.BASELINE_FIXED_COST));
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineFixedCost(int baselineNumber, Number value)
   {
      set(selectField(TaskFieldLists.BASELINE_FIXED_COSTS, baselineNumber), value);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public Number getBaselineFixedCost(int baselineNumber)
   {
      return ((Number) getCachedValue(selectField(TaskFieldLists.BASELINE_FIXED_COSTS, baselineNumber)));
   }

   /**
    * Retrieves the baseline fixed cost accrual.
    *
    * @return fixed cost accrual flag
    */
   public AccrueType getBaselineFixedCostAccrual()
   {
      return ((AccrueType) getCachedValue(TaskField.BASELINE_FIXED_COST_ACCRUAL));
   }

   /**
    * Sets the baseline fixed cost accrual.
    *
    * @param type fixed cost accrual type
    */
   public void setBaselineFixedCostAccrual(AccrueType type)
   {
      set(TaskField.BASELINE_FIXED_COST_ACCRUAL, type);
   }

   /**
    * Set a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @param value baseline value
    */
   public void setBaselineFixedCostAccrual(int baselineNumber, AccrueType value)
   {
      set(selectField(TaskFieldLists.BASELINE_FIXED_COST_ACCRUALS, baselineNumber), value);
   }

   /**
    * Retrieve a baseline value.
    *
    * @param baselineNumber baseline index (1-10)
    * @return baseline value
    */
   public AccrueType getBaselineFixedCostAccrual(int baselineNumber)
   {
      return ((AccrueType) getCachedValue(selectField(TaskFieldLists.BASELINE_FIXED_COST_ACCRUALS, baselineNumber)));
   }

   /**
    * Retrieve the effective calendar for this task. If the task does not have
    * a specific calendar associated with it, fall back to using the default calendar
    * for the project.
    *
    * @return ProjectCalendar instance
    */
   public ProjectCalendar getEffectiveCalendar()
   {
      ProjectCalendar result = getCalendar();
      if (result == null)
      {
         result = getParentFile().getDefaultCalendar();
      }
      return result;
   }

   /**
    * This method allows a predecessor relationship to be removed from this
    * task instance.  It will only delete relationships that exactly match the
    * given targetTask, type and lag time.
    *
    * @param targetTask the predecessor task
    * @param type relation type
    * @param lag relation lag
    * @return returns true if the relation is found and removed
    */
   public boolean removePredecessor(Task targetTask, RelationType type, Duration lag)
   {
      boolean matchFound = false;

      //
      // Retrieve the list of predecessors
      //
      List<Relation> predecessorList = getPredecessors();
      if (!predecessorList.isEmpty())
      {
         //
         // Ensure that we have a valid lag duration
         //
         if (lag == null)
         {
            lag = Duration.getInstance(0, TimeUnit.DAYS);
         }

         //
         // Ensure that there is a predecessor relationship between
         // these two tasks, and remove it.
         //
         matchFound = removeRelation(predecessorList, targetTask, type, lag);

         //
         // If we have removed a predecessor, then we must remove the
         // corresponding successor entry from the target task list
         //
         if (matchFound)
         {
            //
            // Retrieve the list of successors
            //
            List<Relation> successorList = targetTask.getSuccessors();
            if (!successorList.isEmpty())
            {
               //
               // Ensure that there is a successor relationship between
               // these two tasks, and remove it.
               //
               removeRelation(successorList, this, type, lag);
            }
         }
      }

      return matchFound;
   }

   /**
    * Internal method used to locate an remove an item from a list Relations.
    *
    * @param relationList list of Relation instances
    * @param targetTask target relationship task
    * @param type target relationship type
    * @param lag target relationship lag
    * @return true if a relationship was removed
    */
   private boolean removeRelation(List<Relation> relationList, Task targetTask, RelationType type, Duration lag)
   {
      boolean matchFound = false;
      for (Relation relation : relationList)
      {
         if (relation.getTargetTask() == targetTask)
         {
            if (relation.getType() == type && relation.getLag().compareTo(lag) == 0)
            {
               matchFound = relationList.remove(relation);
               break;
            }
         }
      }
      return matchFound;
   }

   /**
    * Maps a field index to a TaskField instance.
    *
    * @param fields array of fields used as the basis for the mapping.
    * @param index required field index
    * @return TaskField instance
    */
   private TaskField selectField(TaskField[] fields, int index)
   {
      if (index < 1 || index > fields.length)
      {
         throw new IllegalArgumentException(index + " is not a valid field index");
      }
      return (fields[index - 1]);
   }

   /**
    * {@inheritDoc}
    */
   @Override public Object getCachedValue(FieldType field)
   {
      return (field == null ? null : m_array[field.getValue()]);
   }

   /**
    * {@inheritDoc}
    */
   @Override public Object getCurrentValue(FieldType field)
   {
      Object result = null;

      if (field != null)
      {
         switch ((TaskField) field)
         {
            case PARENT_TASK_UNIQUE_ID:
            {
               result = m_parent == null ? Integer.valueOf(-1) : m_parent.getUniqueID();
               break;
            }

            case START_VARIANCE:
            {
               result = getStartVariance();
               break;
            }

            case FINISH_VARIANCE:
            {
               result = getFinishVariance();
               break;
            }

            case START_SLACK:
            {
               result = getStartSlack();
               break;
            }

            case FINISH_SLACK:
            {
               result = getFinishSlack();
               break;
            }

            case COST_VARIANCE:
            {
               result = getCostVariance();
               break;
            }

            case DURATION_VARIANCE:
            {
               result = getDurationVariance();
               break;
            }

            case WORK_VARIANCE:
            {
               result = getWorkVariance();
               break;
            }

            case CV:
            {
               result = getCV();
               break;
            }

            case SV:
            {
               result = getSV();
               break;
            }

            case TOTAL_SLACK:
            {
               result = getTotalSlack();
               break;
            }

            case CRITICAL:
            {
               result = Boolean.valueOf(getCritical());
               break;
            }

            case COMPLETE_THROUGH:
            {
               result = getCompleteThrough();
               break;
            }

            default:
            {
               result = m_array[field.getValue()];
               break;
            }
         }
      }

      return (result);
   }

   /**
    * {@inheritDoc}
    */
   @Override public void set(FieldType field, Object value)
   {
      if (field != null)
      {
         int index = field.getValue();
         if (m_eventsEnabled)
         {
            fireFieldChangeEvent((TaskField) field, m_array[index], value);
         }
         m_array[index] = value;
      }
   }

   /**
    * Handle the change in a field value. Reset any cached calculated
    * values affected by this change, pass on the event to any external
    * listeners.
    *
    * @param field field changed
    * @param oldValue old field value
    * @param newValue new field value
    */
   private void fireFieldChangeEvent(TaskField field, Object oldValue, Object newValue)
   {
      //
      // Internal event handling
      //
      switch (field)
      {
         case UNIQUE_ID:
         {
            ProjectFile parent = getParentFile();
            if (oldValue != null)
            {
               parent.getTasks().unmapUniqueID((Integer) oldValue);
            }
            parent.getTasks().mapUniqueID((Integer) newValue, this);
            break;
         }

         case START:
         case BASELINE_START:
         {
            m_array[TaskField.START_VARIANCE.getValue()] = null;
            break;
         }

         case FINISH:
         case BASELINE_FINISH:
         {
            m_array[TaskField.FINISH_VARIANCE.getValue()] = null;
            break;
         }

         case COST:
         case BASELINE_COST:
         {
            m_array[TaskField.COST_VARIANCE.getValue()] = null;
            break;
         }

         case DURATION:
         {
            m_array[TaskField.DURATION_VARIANCE.getValue()] = null;
            m_array[TaskField.COMPLETE_THROUGH.getValue()] = null;
            break;
         }

         case BASELINE_DURATION:
         {
            m_array[TaskField.DURATION_VARIANCE.getValue()] = null;
            break;
         }

         case WORK:
         case BASELINE_WORK:
         {
            m_array[TaskField.WORK_VARIANCE.getValue()] = null;
            break;
         }

         case BCWP:
         case ACWP:
         {
            m_array[TaskField.CV.getValue()] = null;
            m_array[TaskField.SV.getValue()] = null;
            break;
         }

         case BCWS:
         {
            m_array[TaskField.SV.getValue()] = null;
            break;
         }

         case START_SLACK:
         case FINISH_SLACK:
         {
            m_array[TaskField.TOTAL_SLACK.getValue()] = null;
            m_array[TaskField.CRITICAL.getValue()] = null;
            break;
         }

         case EARLY_FINISH:
         case LATE_FINISH:
         {
            m_array[TaskField.FINISH_SLACK.getValue()] = null;
            m_array[TaskField.TOTAL_SLACK.getValue()] = null;
            m_array[TaskField.CRITICAL.getValue()] = null;
            break;
         }

         case EARLY_START:
         case LATE_START:
         {
            m_array[TaskField.START_SLACK.getValue()] = null;
            m_array[TaskField.TOTAL_SLACK.getValue()] = null;
            m_array[TaskField.CRITICAL.getValue()] = null;
            break;
         }

         case ACTUAL_START:
         case PERCENT_COMPLETE:
         {
            m_array[TaskField.COMPLETE_THROUGH.getValue()] = null;
            break;
         }

         default:
         {
            break;
         }
      }

      //
      // External event handling
      //
      if (m_listeners != null)
      {
         for (FieldListener listener : m_listeners)
         {
            listener.fieldChange(this, field, oldValue, newValue);
         }
      }
   }

   /**
    * {@inheritDoc}
    */
   @Override public void addFieldListener(FieldListener listener)
   {
      if (m_listeners == null)
      {
         m_listeners = new ArrayList<>();
      }
      m_listeners.add(listener);
   }

   /**
    * {@inheritDoc}
    */
   @Override public void removeFieldListener(FieldListener listener)
   {
      if (m_listeners != null)
      {
         m_listeners.remove(listener);
      }
   }

   /**
    * This method inserts a name value pair into internal storage.
    *
    * @param field task field
    * @param value attribute value
    */
   private void set(FieldType field, boolean value)
   {
      set(field, (value ? Boolean.TRUE : Boolean.FALSE));
   }

   /**
    * {@inheritDoc}
    */
   @Override public String toString()
   {
      return ("[Task id=" + getID() + " uniqueID=" + getUniqueID() + " name=" + getName() + (getExternalTask() ? " [EXTERNAL uid=" + getSubprojectTaskUniqueID() + " id=" + getSubprojectTaskID() + "]" : "]") + (getSubProject() == null ? "" : (" project=" + getSubProject())));
   }

   /**
    * Utility method used to determine if the supplied task
    * is a predecessor of the current task.
    *
    * @param task potential predecessor task
    * @return Boolean flag
    */
   public boolean isPredecessor(Task task)
   {
      return isRelated(task, getPredecessors());
   }

   /**
    * Utility method used to determine if the supplied task
    * is a successor of the current task.
    *
    * @param task potential successor task
    * @return Boolean flag
    */
   public boolean isSucessor(Task task)
   {
      return isRelated(task, getSuccessors());
   }

   /**
    * Used to determine if a task has child tasks.
    *
    * @return true if the task has child tasks
    */
   public boolean hasChildTasks()
   {
      return !m_children.isEmpty();
   }

   /**
    * Internal method used to test for the existence of a relationship
    * with a task.
    *
    * @param task target task
    * @param list list of relationships
    * @return boolean flag
    */
   private boolean isRelated(Task task, List<Relation> list)
   {
      boolean result = false;
      for (Relation relation : list)
      {
         if (relation.getTargetTask().getUniqueID().intValue() == task.getUniqueID().intValue())
         {
            result = true;
            break;
         }
      }
      return result;
   }

   /**
    * Disable events firing when fields are updated.
    */
   public void disableEvents()
   {
      m_eventsEnabled = false;
   }

   /**
    * Enable events firing when fields are updated. This is the default state.
    */
   public void enableEvents()
   {
      m_eventsEnabled = true;
   }

   /**
    * Array of field values.
    */
   private Object[] m_array = new Object[TaskField.MAX_VALUE];

   /**
    * This is a reference to the parent task, as specified by the
    * outline level.
    */
   private Task m_parent;

   /**
    * This list holds references to all tasks that are children of the
    * current task as specified by the outline level.
    */
   private List<Task> m_children = new ArrayList<>();

   /**
    * List of resource assignments for this task.
    */
   private List<ResourceAssignment> m_assignments = new ArrayList<>();

   /**
    * List of activity codes for this task.
    */
   private List<ActivityCodeValue> m_activityCodes = new ArrayList<>();

   /**
    * Recurring task details associated with this task.
    */
   private RecurringTask m_recurringTask;

   private boolean m_eventsEnabled = true;
   private boolean m_null;
   private boolean m_resumeValid;
   private String m_externalTaskProject;
   private boolean m_expanded = true;
   private List<FieldListener> m_listeners;
}
